| Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed phrasewheel.
2
3 A class, extending wx.TextCtrl, which has a drop-down pick list,
4 automatically filled based on the inital letters typed. Based on the
5 interface of Richard Terry's Visual Basic client
6
7 This is based on seminal work by Ian Haywood <ihaywood@gnu.org>
8 """
9 ############################################################################
10 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmPhraseWheel.py,v $
11 # $Id: gmPhraseWheel.py,v 1.136 2010/02/02 13:55:59 ncq Exp $
12 __version__ = "$Revision: 1.136 $"
13 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>, I.Haywood, S.J.Tan <sjtan@bigpond.com>"
14 __license__ = "GPL"
15
16 # stdlib
17 import string, types, time, sys, re as regex, os.path
18
19
20 # 3rd party
21 import wx
22 import wx.lib.mixins.listctrl as listmixins
23 import wx.lib.pubsub
24
25
26 # GNUmed specific
27 if __name__ == '__main__':
28 sys.path.insert(0, '../../')
29 from Gnumed.pycommon import gmTools
30
31
32 import logging
33 _log = logging.getLogger('macosx')
34
35
36 color_prw_invalid = 'pink'
37 color_prw_valid = None # this is used by code outside this module
38
39 default_phrase_separators = '[;/|]+'
40 default_spelling_word_separators = '[\W\d_]+'
41
42 # those can be used by the <accepted_chars> phrasewheel parameter
43 NUMERIC = '0-9'
44 ALPHANUMERIC = 'a-zA-Z0-9'
45 EMAIL_CHARS = "a-zA-Z0-9\-_@\."
46 WEB_CHARS = "a-zA-Z0-9\.\-_/:"
47
48
49 _timers = []
50 #============================================================
52 """It can be useful to call this early from your shutdown code to avoid hangs on Notify()."""
53 global _timers
54 _log.info('shutting down %s pending timers', len(_timers))
55 for timer in _timers:
56 _log.debug('timer [%s]', timer)
57 timer.Stop()
58 _timers = []
59 #------------------------------------------------------------
61
63 wx.Timer.__init__(self, *args, **kwargs)
64 self.callback = lambda x:x
65 global _timers
66 _timers.append(self)
67
70 #============================================================
71 # FIXME: merge with gmListWidgets
74 try:
75 kwargs['style'] = kwargs['style'] | wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.SIMPLE_BORDER
76 except: pass
77 wx.ListCtrl.__init__(self, *args, **kwargs)
78 listmixins.ListCtrlAutoWidthMixin.__init__(self)
79 #--------------------------------------------------------
81 self.DeleteAllItems()
82 self.__data = items
83 pos = len(items) + 1
84 for item in items:
85 row_num = self.InsertStringItem(pos, label=item['label'])
86 #--------------------------------------------------------
88 sel_idx = self.GetFirstSelected()
89 if sel_idx == -1:
90 return None
91 return self.__data[sel_idx]['data']
92 #--------------------------------------------------------
98 #============================================================
99 # FIXME: cols in pick list
100 # FIXME: snap_to_basename+set selection
101 # FIXME: learn() -> PWL
102 # FIXME: up-arrow: show recent (in-memory) history
103 #----------------------------------------------------------
104 # ideas
105 #----------------------------------------------------------
106 #- display possible completion but highlighted for deletion
107 #(- cycle through possible completions)
108 #- pre-fill selection with SELECT ... LIMIT 25
109 #- async threads for match retrieval instead of timer
110 # - on truncated results return item "..." -> selection forcefully retrieves all matches
111
112 #- generators/yield()
113 #- OnChar() - process a char event
114
115 # split input into words and match components against known phrases
116
117 # make special list window:
118 # - deletion of items
119 # - highlight matched parts
120 # - faster scrolling
121 # - wxEditableListBox ?
122
123 # - if non-learning (i.e. fast select only): autocomplete with match
124 # and move cursor to end of match
125 #-----------------------------------------------------------------------------------------------
126 # darn ! this clever hack won't work since we may have crossed a search location threshold
127 #----
128 # #self.__prevFragment = "XXXXXXXXXXXXXXXXXX-very-unlikely--------------XXXXXXXXXXXXXXX"
129 # #self.__prevMatches = [] # a list of tuples (ID, listbox name, weight)
130 #
131 # # is the current fragment just a longer version of the previous fragment ?
132 # if string.find(aFragment, self.__prevFragment) == 0:
133 # # we then need to search in the previous matches only
134 # for prevMatch in self.__prevMatches:
135 # if string.find(prevMatch[1], aFragment) == 0:
136 # matches.append(prevMatch)
137 # # remember current matches
138 # self.__prefMatches = matches
139 # # no matches found
140 # if len(matches) == 0:
141 # return [(1,_('*no matching items found*'),1)]
142 # else:
143 # return matches
144 #----
145 #TODO:
146 # - see spincontrol for list box handling
147 # stop list (list of negatives): "an" -> "animal" but not "and"
148 #-----
149 #> > remember, you should be searching on either weighted data, or in some
150 #> > situations a start string search on indexed data
151 #>
152 #> Can you be a bit more specific on this ?
153
154 #seaching ones own previous text entered would usually be instring but
155 #weighted (ie the phrases you use the most auto filter to the top)
156
157 #Searching a drug database for a drug brand name is usually more
158 #functional if it does a start string search, not an instring search which is
159 #much slower and usually unecesary. There are many other examples but trust
160 #me one needs both
161 #-----
163 """Widget for smart guessing of user fields, after Richard Terry's interface.
164
165 - VB implementation by Richard Terry
166 - Python port by Ian Haywood for GNUmed
167 - enhanced by Karsten Hilbert for GNUmed
168 - enhanced by Ian Haywood for aumed
169 - enhanced by Karsten Hilbert for GNUmed
170
171 @param matcher: a class used to find matches for the current input
172 @type matcher: a L{match provider<Gnumed.pycommon.gmMatchProvider.cMatchProvider>}
173 instance or C{None}
174
175 @param selection_only: whether free-text can be entered without associated data
176 @type selection_only: boolean
177
178 @param capitalisation_mode: how to auto-capitalize input, valid values
179 are found in L{capitalize()<Gnumed.pycommon.gmTools.capitalize>}
180 @type capitalisation_mode: integer
181
182 @param accepted_chars: a regex pattern defining the characters
183 acceptable in the input string, if None no checking is performed
184 @type accepted_chars: None or a string holding a valid regex pattern
185
186 @param final_regex: when the control loses focus the input is
187 checked against this regular expression
188 @type final_regex: a string holding a valid regex pattern
189
190 @param phrase_separators: if not None, input is split into phrases
191 at boundaries defined by this regex and matching/spellchecking
192 is performed on the phrase the cursor is in only
193 @type phrase_separators: None or a string holding a valid regex pattern
194
195 @param navigate_after_selection: whether or not to immediately
196 navigate to the widget next-in-tab-order after selecting an
197 item from the dropdown picklist
198 @type navigate_after_selection: boolean
199
200 @param speller: if not None used to spellcheck the current input
201 and to retrieve suggested replacements/completions
202 @type speller: None or a L{enchant Dict<enchant>} descendant
203
204 @param picklist_delay: this much time of user inactivity must have
205 passed before the input related smarts kick in and the drop
206 down pick list is shown
207 @type picklist_delay: integer (milliseconds)
208 """
210
211 # behaviour
212 self.matcher = None
213 self.selection_only = False
214 self.selection_only_error_msg = _('You must select a value from the picklist or type an exact match.')
215 self.capitalisation_mode = gmTools.CAPS_NONE
216 self.accepted_chars = None
217 self.final_regex = '.*'
218 self.final_regex_error_msg = _('The content is invalid. It must match the regular expression: [%%s]. <%s>') % self.__class__.__name__
219 self.phrase_separators = default_phrase_separators
220 self.navigate_after_selection = False
221 self.speller = None
222 self.speller_word_separators = default_spelling_word_separators
223 self.picklist_delay = 150 # milliseconds
224
225 # state tracking
226 self._has_focus = False
227 self.suppress_text_update_smarts = False
228 self.__current_matches = []
229 self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
230 self.input2match = ''
231 self.left_part = ''
232 self.right_part = ''
233 self.data = None
234
235 self._on_selection_callbacks = []
236 self._on_lose_focus_callbacks = []
237 self._on_set_focus_callbacks = []
238 self._on_modified_callbacks = []
239
240 try:
241 kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_TAB
242 except KeyError:
243 kwargs['style'] = wx.TE_PROCESS_TAB
244 wx.TextCtrl.__init__(self, parent, id, **kwargs)
245
246 self.__non_edit_font = self.GetFont()
247 self.__color_valid = self.GetBackgroundColour()
248 global color_prw_valid
249 if color_prw_valid is None:
250 color_prw_valid = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)
251
252 self.__init_dropdown(parent = parent)
253 self.__register_events()
254 self.__init_timer()
255 #--------------------------------------------------------
256 # external API
257 #--------------------------------------------------------
259 """
260 Add a callback for invocation when a picklist item is selected.
261
262 The callback will be invoked whenever an item is selected
263 from the picklist. The associated data is passed in as
264 a single parameter. Callbacks must be able to cope with
265 None as the data parameter as that is sent whenever the
266 user changes a previously selected value.
267 """
268 if not callable(callback):
269 raise ValueError('[add_callback_on_selection]: ignoring callback [%s], it is not callable' % callback)
270
271 self._on_selection_callbacks.append(callback)
272 #---------------------------------------------------------
274 """
275 Add a callback for invocation when getting focus.
276 """
277 if not callable(callback):
278 raise ValueError('[add_callback_on_set_focus]: ignoring callback [%s] - not callable' % callback)
279
280 self._on_set_focus_callbacks.append(callback)
281 #---------------------------------------------------------
283 """
284 Add a callback for invocation when losing focus.
285 """
286 if not callable(callback):
287 raise ValueError('[add_callback_on_lose_focus]: ignoring callback [%s] - not callable' % callback)
288
289 self._on_lose_focus_callbacks.append(callback)
290 #---------------------------------------------------------
292 """
293 Add a callback for invocation when the content is modified.
294 """
295 if not callable(callback):
296 raise ValueError('[add_callback_on_modified]: ignoring callback [%s] - not callable' % callback)
297
298 self._on_modified_callbacks.append(callback)
299 #---------------------------------------------------------
301 """
302 Set the data and thereby set the value, too.
303
304 If you call SetData() you better be prepared
305 doing a scan of the entire potential match space.
306
307 The whole thing will only work if data is found
308 in the match space anyways.
309 """
310 if self.matcher is None:
311 matched, matches = (False, [])
312 else:
313 matched, matches = self.matcher.getMatches('*')
314
315 if self.selection_only:
316 if not matched or (len(matches) == 0):
317 return False
318
319 for match in matches:
320 if match['data'] == data:
321 self.display_as_valid(valid = True)
322 self.suppress_text_update_smarts = True
323 wx.TextCtrl.SetValue(self, match['label'])
324 self.data = data
325 return True
326
327 # no match found ...
328 if self.selection_only:
329 return False
330
331 self.data = data
332 self.display_as_valid(valid = True)
333 return True
334 #---------------------------------------------------------
336 """Retrieve the data associated with the displayed string.
337 """
338 if self.data is None:
339 if can_create:
340 self._create_data()
341 return self.data
342 #---------------------------------------------------------
344
345 self.suppress_text_update_smarts = suppress_smarts
346
347 if data is not None:
348 self.suppress_text_update_smarts = True
349 self.data = data
350 wx.TextCtrl.SetValue(self, value)
351 self.display_as_valid(valid = True)
352
353 # if data already available
354 if self.data is not None:
355 return True
356
357 if value == u'' and not self.selection_only:
358 return True
359
360 # or try to find data from matches
361 if self.matcher is None:
362 stat, matches = (False, [])
363 else:
364 stat, matches = self.matcher.getMatches(aFragment = value)
365
366 for match in matches:
367 if match['label'] == value:
368 self.data = match['data']
369 return True
370
371 # not found
372 if self.selection_only:
373 self.display_as_valid(valid = False)
374 return False
375
376 return True
377 #--------------------------------------------------------
381 #---------------------------------------------------------
385 #--------------------------------------------------------
387 # FIXME: use Debian's wgerman-medical as "personal" wordlist if available
388 try:
389 import enchant
390 except ImportError:
391 self.speller = None
392 return False
393 try:
394 self.speller = enchant.DictWithPWL(None, os.path.expanduser(os.path.join('~', '.gnumed', 'spellcheck', 'wordlist.pwl')))
395 except enchant.DictNotFoundError:
396 self.speller = None
397 return False
398 return True
399 #--------------------------------------------------------
401 if valid is True:
402 self.SetBackgroundColour(self.__color_valid)
403 elif valid is False:
404 self.SetBackgroundColour(color_prw_invalid)
405 else:
406 raise ArgumentError(u'<valid> must be True or False')
407 self.Refresh()
408 #--------------------------------------------------------
409 # internal API
410 #--------------------------------------------------------
411 # picklist handling
412 #--------------------------------------------------------
414 szr_dropdown = None
415 try:
416 #raise NotImplementedError # for testing
417 self.__dropdown_needs_relative_position = False
418 self.__picklist_dropdown = wx.PopupWindow(parent)
419 list_parent = self.__picklist_dropdown
420 self.__use_fake_popup = False
421 except NotImplementedError:
422 self.__use_fake_popup = True
423
424 # on MacOSX wx.PopupWindow is not implemented, so emulate it
425 add_picklist_to_sizer = True
426 szr_dropdown = wx.BoxSizer(wx.VERTICAL)
427
428 # using wx.MiniFrame
429 self.__dropdown_needs_relative_position = False
430 self.__picklist_dropdown = wx.MiniFrame (
431 parent = parent,
432 id = -1,
433 style = wx.SIMPLE_BORDER | wx.FRAME_FLOAT_ON_PARENT | wx.FRAME_NO_TASKBAR | wx.POPUP_WINDOW
434 )
435 scroll_win = wx.ScrolledWindow(parent = self.__picklist_dropdown, style = wx.NO_BORDER)
436 scroll_win.SetSizer(szr_dropdown)
437 list_parent = scroll_win
438
439 # using wx.Window
440 #self.__dropdown_needs_relative_position = True
441 #self.__picklist_dropdown = wx.ScrolledWindow(parent=parent, style = wx.RAISED_BORDER)
442 #self.__picklist_dropdown.SetSizer(szr_dropdown)
443 #list_parent = self.__picklist_dropdown
444
445 self.mac_log('dropdown parent: %s' % self.__picklist_dropdown.GetParent())
446
447 # FIXME: support optional headers
448 # if kwargs['show_list_headers']:
449 # flags = 0
450 # else:
451 # flags = wx.LC_NO_HEADER
452 self._picklist = cPhraseWheelListCtrl (
453 list_parent,
454 style = wx.LC_NO_HEADER
455 )
456 self._picklist.InsertColumn(0, '')
457
458 if szr_dropdown is not None:
459 szr_dropdown.Add(self._picklist, 1, wx.EXPAND)
460
461 self.__picklist_dropdown.Hide()
462 #--------------------------------------------------------
464 """Display the pick list."""
465
466 border_width = 4
467 extra_height = 25
468
469 self.__picklist_dropdown.Hide()
470
471 # this helps if the current input was already selected from the
472 # list but still is the substring of another pick list item
473 if self.data is not None:
474 return
475
476 if not self._has_focus:
477 return
478
479 if len(self.__current_matches) == 0:
480 return
481
482 # if only one match and text == match
483 if len(self.__current_matches) == 1:
484 if self.__current_matches[0]['label'] == self.input2match:
485 self.data = self.__current_matches[0]['data']
486 return
487
488 # recalculate size
489 rows = len(self.__current_matches)
490 if rows < 2: # 2 rows minimum
491 rows = 2
492 if rows > 20: # 20 rows maximum
493 rows = 20
494 self.mac_log('dropdown needs rows: %s' % rows)
495 dropdown_size = self.__picklist_dropdown.GetSize()
496 pw_size = self.GetSize()
497 dropdown_size.SetWidth(pw_size.width)
498 dropdown_size.SetHeight (
499 (pw_size.height * rows)
500 + border_width
501 + extra_height
502 )
503
504 # recalculate position
505 (pw_x_abs, pw_y_abs) = self.ClientToScreenXY(0,0)
506 self.mac_log('phrasewheel position (on screen): x:%s-%s, y:%s-%s' % (pw_x_abs, (pw_x_abs+pw_size.width), pw_y_abs, (pw_y_abs+pw_size.height)))
507 dropdown_new_x = pw_x_abs
508 dropdown_new_y = pw_y_abs + pw_size.height
509 self.mac_log('desired dropdown position (on screen): x:%s-%s, y:%s-%s' % (dropdown_new_x, (dropdown_new_x+dropdown_size.width), dropdown_new_y, (dropdown_new_y+dropdown_size.height)))
510 self.mac_log('desired dropdown size: %s' % dropdown_size)
511
512 # reaches beyond screen ?
513 if (dropdown_new_y + dropdown_size.height) > self._screenheight:
514 self.mac_log('dropdown extends offscreen (screen max y: %s)' % self._screenheight)
515 max_height = self._screenheight - dropdown_new_y - 4
516 self.mac_log('max dropdown height would be: %s' % max_height)
517 if max_height > ((pw_size.height * 2) + 4):
518 dropdown_size.SetHeight(max_height)
519 self.mac_log('possible dropdown position (on screen): x:%s-%s, y:%s-%s' % (dropdown_new_x, (dropdown_new_x+dropdown_size.width), dropdown_new_y, (dropdown_new_y+dropdown_size.height)))
520 self.mac_log('possible dropdown size: %s' % dropdown_size)
521
522 # now set dimensions
523 self.__picklist_dropdown.SetSize(dropdown_size)
524 self._picklist.SetSize(self.__picklist_dropdown.GetClientSize())
525 self.mac_log('pick list size set to: %s' % self.__picklist_dropdown.GetSize())
526 if self.__dropdown_needs_relative_position:
527 dropdown_new_x, dropdown_new_y = self.__picklist_dropdown.GetParent().ScreenToClientXY(dropdown_new_x, dropdown_new_y)
528 self.__picklist_dropdown.MoveXY(dropdown_new_x, dropdown_new_y)
529
530 # select first value
531 self._picklist.Select(0)
532
533 # and show it
534 self.__picklist_dropdown.Show(True)
535
536 dd_tl = self.__picklist_dropdown.ClientToScreenXY(0,0)
537 dd_size = self.__picklist_dropdown.GetSize()
538 dd_br = self.__picklist_dropdown.ClientToScreenXY(dd_size.width, dd_size.height)
539 self.mac_log('dropdown placement now (on screen): x:%s-%s, y:%s-%s' % (dd_tl[0], dd_br[0], dd_tl[1], dd_br[1]))
540 #--------------------------------------------------------
542 """Hide the pick list."""
543 self.__picklist_dropdown.Hide() # dismiss the dropdown list window
544 #--------------------------------------------------------
546 if old_row_idx is not None:
547 pass # FIXME: do we need unselect here ? Select() should do it for us
548 self._picklist.Select(new_row_idx)
549 self._picklist.EnsureVisible(new_row_idx)
550 #---------------------------------------------------------
552 """Get the matches for the currently typed input fragment."""
553
554 self.input2match = val
555 if self.input2match is None:
556 if self.__phrase_separators is None:
557 self.input2match = self.GetValue().strip()
558 else:
559 # get current(ly relevant part of) input
560 entire_input = self.GetValue()
561 cursor_pos = self.GetInsertionPoint()
562 left_of_cursor = entire_input[:cursor_pos]
563 right_of_cursor = entire_input[cursor_pos:]
564 left_boundary = self.__phrase_separators.search(left_of_cursor)
565 if left_boundary is not None:
566 phrase_start = left_boundary.end()
567 else:
568 phrase_start = 0
569 self.left_part = entire_input[:phrase_start]
570 # find next phrase separator after cursor position
571 right_boundary = self.__phrase_separators.search(right_of_cursor)
572 if right_boundary is not None:
573 phrase_end = cursor_pos + (right_boundary.start() - 1)
574 else:
575 phrase_end = len(entire_input) - 1
576 self.right_part = entire_input[phrase_end+1:]
577 self.input2match = entire_input[phrase_start:phrase_end+1]
578
579 # get all currently matching items
580 if self.matcher is not None:
581 matched, self.__current_matches = self.matcher.getMatches(self.input2match)
582 self._picklist.SetItems(self.__current_matches)
583
584 # no matches found: might simply be due to a typo, so spellcheck
585 if len(self.__current_matches) == 0:
586 if self.speller is not None:
587 # filter out the last word
588 word = regex.split(self.__speller_word_separators, self.input2match)[-1]
589 if word.strip() != u'':
590 success = False
591 try:
592 success = self.speller.check(word)
593 except:
594 _log.exception('had to disable enchant spell checker')
595 self.speller = None
596 if success:
597 spells = self.speller.suggest(word)
598 truncated_input2match = self.input2match[:self.input2match.rindex(word)]
599 for spell in spells:
600 self.__current_matches.append({'label': truncated_input2match + spell, 'data': None})
601 self._picklist.SetItems(self.__current_matches)
602 #--------------------------------------------------------
605 #--------------------------------------------------------
606 # internal helpers: GUI
607 #--------------------------------------------------------
609 """Called when the user pressed <ENTER>."""
610 if self.__picklist_dropdown.IsShown():
611 self._on_list_item_selected()
612 else:
613 # FIXME: check for errors before navigation
614 self.Navigate()
615 #--------------------------------------------------------
617
618 if self.__picklist_dropdown.IsShown():
619 selected = self._picklist.GetFirstSelected()
620 if selected < (len(self.__current_matches) - 1):
621 self.__select_picklist_row(selected+1, selected)
622
623 # if we don't yet have a pick list: open new pick list
624 # (this can happen when we TAB into a field pre-filled
625 # with the top-weighted contextual data but want to
626 # select another contextual item)
627 else:
628 self.__timer.Stop()
629 if self.GetValue().strip() == u'':
630 self.__update_matches_in_picklist(val='*')
631 else:
632 self.__update_matches_in_picklist()
633 self._show_picklist()
634 #--------------------------------------------------------
636 if self.__picklist_dropdown.IsShown():
637 selected = self._picklist.GetFirstSelected()
638 if selected > 0:
639 self.__select_picklist_row(selected-1, selected)
640 else:
641 # FIXME: input history ?
642 pass
643 #--------------------------------------------------------
645 """Under certain circumstances takes special action on TAB.
646
647 returns:
648 True: TAB was handled
649 False: TAB was not handled
650 """
651 if not self.__picklist_dropdown.IsShown():
652 return False
653
654 if len(self.__current_matches) != 1:
655 return False
656
657 if not self.selection_only:
658 return False
659
660 self.__select_picklist_row(new_row_idx=0)
661 self._on_list_item_selected()
662
663 return True
664 #--------------------------------------------------------
665 # internal helpers: logic
666 #--------------------------------------------------------
669 #--------------------------------------------------------
671 # if undefined accept all chars
672 if self.accepted_chars is None:
673 return True
674 return (self.__accepted_chars.match(char) is not None)
675 #--------------------------------------------------------
677 if accepted_chars is None:
678 self.__accepted_chars = None
679 else:
680 self.__accepted_chars = regex.compile(accepted_chars)
681
686
687 accepted_chars = property(_get_accepted_chars, _set_accepted_chars)
688 #--------------------------------------------------------
690 self.__final_regex = regex.compile(final_regex, flags = regex.LOCALE | regex.UNICODE)
691
694
695 final_regex = property(_get_final_regex, _set_final_regex)
696 #--------------------------------------------------------
698 self.__final_regex_error_msg = msg % self.final_regex
699
702
703 final_regex_error_msg = property(_get_final_regex_error_msg, _set_final_regex_error_msg)
704 #--------------------------------------------------------
706 if phrase_separators is None:
707 self.__phrase_separators = None
708 else:
709 self.__phrase_separators = regex.compile(phrase_separators, flags = regex.LOCALE | regex.UNICODE)
710
712 if self.__phrase_separators is None:
713 return None
714 return self.__phrase_separators.pattern
715
716 phrase_separators = property(_get_phrase_separators, _set_phrase_separators)
717 #--------------------------------------------------------
719 if word_separators is None:
720 self.__speller_word_separators = regex.compile('[\W\d_]+', flags = regex.LOCALE | regex.UNICODE)
721 else:
722 self.__speller_word_separators = regex.compile(word_separators, flags = regex.LOCALE | regex.UNICODE)
723
726
727 speller_word_separators = property(_get_speller_word_separators, _set_speller_word_separators)
728 #--------------------------------------------------------
730 self.__timer = _cPRWTimer()
731 self.__timer.callback = self._on_timer_fired
732 # initially stopped
733 self.__timer.Stop()
734 #--------------------------------------------------------
736 """Callback for delayed match retrieval timer.
737
738 if we end up here:
739 - delay has passed without user input
740 - the value in the input field has not changed since the timer started
741 """
742 # update matches according to current input
743 self.__update_matches_in_picklist()
744
745 # we now have either:
746 # - all possible items (within reasonable limits) if input was '*'
747 # - all matching items
748 # - an empty match list if no matches were found
749 # also, our picklist is refilled and sorted according to weight
750
751 wx.CallAfter(self._show_picklist)
752 #--------------------------------------------------------
753 # event handling
754 #--------------------------------------------------------
756 wx.EVT_TEXT(self, self.GetId(), self._on_text_update)
757 wx.EVT_KEY_DOWN (self, self._on_key_down)
758 wx.EVT_SET_FOCUS(self, self._on_set_focus)
759 wx.EVT_KILL_FOCUS(self, self._on_lose_focus)
760 self._picklist.Bind(wx.EVT_LEFT_DCLICK, self._on_list_item_selected)
761 #--------------------------------------------------------
763 """Gets called when user selected a list item."""
764
765 self._hide_picklist()
766 self.display_as_valid(valid = True)
767
768 data = self._picklist.GetSelectedItemData() # just so that _picklist_selection2display_string can use it
769 if data is None:
770 return
771
772 self.data = data
773
774 # update our display
775 self.suppress_text_update_smarts = True
776 if self.__phrase_separators is not None:
777 wx.TextCtrl.SetValue(self, u'%s%s%s' % (self.left_part, self._picklist_selection2display_string(), self.right_part))
778 else:
779 wx.TextCtrl.SetValue(self, self._picklist_selection2display_string())
780
781 self.data = self._picklist.GetSelectedItemData()
782 self.MarkDirty()
783
784 # and tell the listeners about the user's selection
785 for callback in self._on_selection_callbacks:
786 callback(self.data)
787
788 if self.navigate_after_selection:
789 self.Navigate()
790 else:
791 self.SetInsertionPoint(self.GetLastPosition())
792
793 return
794 #--------------------------------------------------------
796 """Is called when a key is pressed."""
797
798 keycode = event.GetKeyCode()
799
800 if keycode == wx.WXK_DOWN:
801 self.__on_cursor_down()
802 return
803
804 if keycode == wx.WXK_UP:
805 self.__on_cursor_up()
806 return
807
808 if keycode == wx.WXK_RETURN:
809 self._on_enter()
810 return
811
812 if keycode == wx.WXK_TAB:
813 if event.ShiftDown():
814 self.Navigate(flags = wx.NavigationKeyEvent.IsBackward)
815 return
816 self.__on_tab()
817 self.Navigate(flags = wx.NavigationKeyEvent.IsForward)
818 return
819
820 # FIXME: need PAGE UP/DOWN//POS1/END here to move in picklist
821 if keycode in [wx.WXK_SHIFT, wx.WXK_BACK, wx.WXK_DELETE, wx.WXK_LEFT, wx.WXK_RIGHT]:
822 pass
823
824 # need to handle all non-character key presses *before* this check
825 elif not self.__char_is_allowed(char = unichr(event.GetUnicodeKey())):
826 # FIXME: configure ?
827 wx.Bell()
828 # FIXME: display error message ? Richard doesn't ...
829 return
830
831 event.Skip()
832 return
833 #--------------------------------------------------------
835 """Internal handler for wx.EVT_TEXT.
836
837 Called when text was changed by user or SetValue().
838 """
839 if self.suppress_text_update_smarts:
840 self.suppress_text_update_smarts = False
841 return
842
843 self.data = None
844 self.__current_matches = []
845
846 # if empty string then hide list dropdown window
847 # we also don't need a timer event then
848 val = self.GetValue().strip()
849 ins_point = self.GetInsertionPoint()
850 if val == u'':
851 self._hide_picklist()
852 self.__timer.Stop()
853 else:
854 new_val = gmTools.capitalize(text = val, mode = self.capitalisation_mode)
855 if new_val != val:
856 self.suppress_text_update_smarts = True
857 wx.TextCtrl.SetValue(self, new_val)
858 if ins_point > len(new_val):
859 self.SetInsertionPointEnd()
860 else:
861 self.SetInsertionPoint(ins_point)
862 # FIXME: SetSelection() ?
863
864 # start timer for delayed match retrieval
865 self.__timer.Start(oneShot = True, milliseconds = self.picklist_delay)
866
867 # notify interested parties
868 for callback in self._on_modified_callbacks:
869 callback()
870
871 return
872 #--------------------------------------------------------
874
875 self._has_focus = True
876 event.Skip()
877
878 self.__non_edit_font = self.GetFont()
879 edit_font = self.GetFont()
880 edit_font.SetPointSize(pointSize = self.__non_edit_font.GetPointSize() + 1)
881 self.SetFont(edit_font)
882 self.Refresh()
883
884 # notify interested parties
885 for callback in self._on_set_focus_callbacks:
886 callback()
887
888 self.__timer.Start(oneShot = True, milliseconds = self.picklist_delay)
889 return True
890 #--------------------------------------------------------
892 """Do stuff when leaving the control.
893
894 The user has had her say, so don't second guess
895 intentions but do report error conditions.
896 """
897 self._has_focus = False
898
899 # don't need timer and pick list anymore
900 self.__timer.Stop()
901 self._hide_picklist()
902
903 # unset selection
904 self.SetSelection(1,1)
905
906 self.SetFont(self.__non_edit_font)
907 self.Refresh()
908
909 is_valid = True
910
911 # the user may have typed a phrase that is an exact match,
912 # however, just typing it won't associate data from the
913 # picklist, so do that now
914 if self.data is None:
915 val = self.GetValue().strip()
916 if val != u'':
917 self.__update_matches_in_picklist()
918 for match in self.__current_matches:
919 if match['label'] == val:
920 self.data = match['data']
921 self.MarkDirty()
922 break
923
924 # no exact match found
925 if self.data is None:
926 if self.selection_only:
927 wx.lib.pubsub.Publisher().sendMessage (
928 topic = 'statustext',
929 data = {'msg': self.selection_only_error_msg}
930 )
931 is_valid = False
932
933 # check value against final_regex if any given
934 if self.__final_regex.match(self.GetValue().strip()) is None:
935 wx.lib.pubsub.Publisher().sendMessage (
936 topic = 'statustext',
937 data = {'msg': self.final_regex_error_msg}
938 )
939 is_valid = False
940
941 self.display_as_valid(valid = is_valid)
942
943 # notify interested parties
944 for callback in self._on_lose_focus_callbacks:
945 callback()
946
947 event.Skip()
948 return True
949 #----------------------------------------------------
953 #--------------------------------------------------------
954 # MAIN
955 #--------------------------------------------------------
956 if __name__ == '__main__':
957 from Gnumed.pycommon import gmI18N
958 gmI18N.activate_locale()
959 gmI18N.install_domain(domain='gnumed')
960
961 from Gnumed.pycommon import gmPG2, gmMatchProvider
962
963 prw = None
964 #--------------------------------------------------------
966 print "got focus:"
967 print "value:", prw.GetValue()
968 print "data :", prw.GetData()
969 return True
970 #--------------------------------------------------------
972 print "lost focus:"
973 print "value:", prw.GetValue()
974 print "data :", prw.GetData()
975 return True
976 #--------------------------------------------------------
978 print "modified:"
979 print "value:", prw.GetValue()
980 print "data :", prw.GetData()
981 return True
982 #--------------------------------------------------------
984 print "selected:"
985 print "value:", prw.GetValue()
986 print "data :", prw.GetData()
987 return True
988 #--------------------------------------------------------
990 app = wx.PyWidgetTester(size = (200, 50))
991
992 items = [ {'data':1, 'label':"Bloggs"},
993 {'data':2, 'label':"Baker"},
994 {'data':3, 'label':"Jones"},
995 {'data':4, 'label':"Judson"},
996 {'data':5, 'label':"Jacobs"},
997 {'data':6, 'label':"Judson-Jacobs"}
998 ]
999
1000 mp = gmMatchProvider.cMatchProvider_FixedList(items)
1001 # do NOT treat "-" as a word separator here as there are names like "asa-sismussen"
1002 mp.word_separators = '[ \t=+&:@]+'
1003 global prw
1004 prw = cPhraseWheel(parent = app.frame, id = -1)
1005 prw.matcher = mp
1006 prw.capitalisation_mode = gmTools.CAPS_NAMES
1007 prw.add_callback_on_set_focus(callback=display_values_set_focus)
1008 prw.add_callback_on_modified(callback=display_values_modified)
1009 prw.add_callback_on_lose_focus(callback=display_values_lose_focus)
1010 prw.add_callback_on_selection(callback=display_values_selected)
1011
1012 app.frame.Show(True)
1013 app.MainLoop()
1014
1015 return True
1016 #--------------------------------------------------------
1018 print "Do you want to test the database connected phrase wheel ?"
1019 yes_no = raw_input('y/n: ')
1020 if yes_no != 'y':
1021 return True
1022
1023 gmPG2.get_connection()
1024 # FIXME: add callbacks
1025 # FIXME: add context
1026 query = u'select code, name from dem.country where _(name) %(fragment_condition)s'
1027 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query])
1028 app = wx.PyWidgetTester(size = (200, 50))
1029 global prw
1030 prw = cPhraseWheel(parent = app.frame, id = -1)
1031 prw.matcher = mp
1032
1033 app.frame.Show(True)
1034 app.MainLoop()
1035
1036 return True
1037 #--------------------------------------------------------
1039 gmPG2.get_connection()
1040 query = u"select pk_identity, firstnames || ' ' || lastnames || ' ' || dob::text as pat_name from dem.v_basic_person where firstnames || lastnames %(fragment_condition)s"
1041
1042 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query])
1043 app = wx.PyWidgetTester(size = (200, 50))
1044 global prw
1045 prw = cPhraseWheel(parent = app.frame, id = -1)
1046 prw.matcher = mp
1047
1048 app.frame.Show(True)
1049 app.MainLoop()
1050
1051 return True
1052 #--------------------------------------------------------
1054 app = wx.PyWidgetTester(size = (200, 50))
1055
1056 global prw
1057 prw = cPhraseWheel(parent = app.frame, id = -1)
1058
1059 prw.add_callback_on_set_focus(callback=display_values_set_focus)
1060 prw.add_callback_on_modified(callback=display_values_modified)
1061 prw.add_callback_on_lose_focus(callback=display_values_lose_focus)
1062 prw.add_callback_on_selection(callback=display_values_selected)
1063
1064 prw.enable_default_spellchecker()
1065
1066 app.frame.Show(True)
1067 app.MainLoop()
1068
1069 return True
1070 #--------------------------------------------------------
1071 # test_prw_fixed_list()
1072 # test_prw_sql2()
1073 test_spell_checking_prw()
1074 # test_prw_patients()
1075
1076 #==================================================
1077 # $Log: gmPhraseWheel.py,v $
1078 # Revision 1.136 2010/02/02 13:55:59 ncq
1079 # - add generic support for can_create in GetData()
1080 #
1081 # Revision 1.135 2009/11/15 01:10:53 ncq
1082 # - cleanup
1083 #
1084 # Revision 1.134 2009/07/23 16:41:42 ncq
1085 # - support custom error msg on final regex mismatch
1086 #
1087 # Revision 1.133 2009/04/24 12:06:01 ncq
1088 # - fix application of final regex: if it was compiled with LOCALE/UNICODE
1089 # one *cannot* apply those flags again on matching !
1090 #
1091 # Revision 1.132 2009/04/19 22:27:36 ncq
1092 # - enlarge edit font by 1 point only
1093 #
1094 # Revision 1.131 2009/04/03 09:52:10 ncq
1095 # - add explicit shutdown for timers
1096 # - self-handle timers
1097 # - a bit of cleanup
1098 #
1099 # Revision 1.130 2009/03/31 15:08:09 ncq
1100 # - removed gmTimer dependancy
1101 #
1102 # Revision 1.129 2009/03/31 14:38:13 ncq
1103 # - rip out gmDispatcher and use wx.lib.pubsub
1104 #
1105 # Revision 1.128 2009/03/01 18:18:50 ncq
1106 # - factor out default phrase separators/spelling word separators
1107 #
1108 # Revision 1.127 2009/02/24 10:16:48 ncq
1109 # - don't hiccup when spell checker hiccups, simply disable it
1110 #
1111 # Revision 1.126 2009/02/04 21:47:54 ncq
1112 # - cleanup
1113 #
1114 # Revision 1.125 2008/10/12 16:32:40 ncq
1115 # - make more robust when getting data of selected item
1116 #
1117 # Revision 1.124 2008/08/15 15:57:37 ncq
1118 # - enchant doesn't like spellchecking '' anymore
1119 #
1120 # Revision 1.123 2008/07/13 16:14:00 ncq
1121 # - outside code uses color_prw_valid, so leave it and add a comment
1122 #
1123 # Revision 1.122 2008/07/07 11:39:21 ncq
1124 # - separate fake_popup from needs_relative_pos flag
1125 #
1126 # Revision 1.121 2008/06/26 17:03:53 ncq
1127 # - use a wxMiniFrame instead of a wx.Window when emulating wx.PopupWindow
1128 # - adjust for some extra space needed by the wx.MiniFrame
1129 #
1130 # Revision 1.120 2008/06/18 15:48:21 ncq
1131 # - support valid/invalid coloring via display_as_valid
1132 # - cleanup init flow
1133 #
1134 # Revision 1.119 2008/06/15 20:40:43 ncq
1135 # - adjust test suite to match provider properties
1136 #
1137 # Revision 1.118 2008/06/09 15:36:39 ncq
1138 # - increase font size by 2 points when editing
1139 #
1140 # Revision 1.117 2008/05/14 13:46:37 ncq
1141 # - better logging
1142 #
1143 # Revision 1.116 2008/05/13 14:15:16 ncq
1144 # - TAB = select-single-match only when selection_only True
1145 # - improve wxPopupWindow emulation
1146 #
1147 # Revision 1.115 2008/05/07 15:21:44 ncq
1148 # - support suppress smarts argument to SetText
1149 #
1150 # Revision 1.114 2008/04/26 16:29:15 ncq
1151 # - missing if
1152 #
1153 # Revision 1.113 2008/04/26 10:06:37 ncq
1154 # - on MacOSX use relative position for popup window
1155 #
1156 # Revision 1.112 2008/04/26 09:30:28 ncq
1157 # - instrument phrasewheel to exhibit Mac problem
1158 # with dropdown placement
1159 #
1160 # Revision 1.111 2008/01/30 14:03:42 ncq
1161 # - use signal names directly
1162 # - switch to std lib logging
1163 #
1164 # Revision 1.110 2007/10/29 11:30:21 ncq
1165 # - rephrase TODOs
1166 #
1167 # Revision 1.109 2007/09/02 20:56:30 ncq
1168 # - cleanup
1169 #
1170 # Revision 1.108 2007/08/12 00:12:41 ncq
1171 # - no more gmSignals.py
1172 #
1173 # Revision 1.107 2007/07/10 20:27:27 ncq
1174 # - install_domain() arg consolidation
1175 #
1176 # Revision 1.106 2007/07/03 16:03:04 ncq
1177 # - cleanup
1178 # - compile final_regex_error_msg just before using it
1179 # since self.final_regex can have changed
1180 #
1181 # Revision 1.105 2007/05/14 14:43:11 ncq
1182 # - allow TAB to select item from picklist if only one match available
1183 #
1184 # Revision 1.104 2007/05/14 13:11:25 ncq
1185 # - use statustext() signal
1186 #
1187 # Revision 1.103 2007/04/19 13:14:30 ncq
1188 # - don't fail input if enchant/aspell installed but no dict available ...
1189 #
1190 # Revision 1.102 2007/04/02 15:16:55 ncq
1191 # - make spell checker act on last word of phrase only
1192 # - to that end add property speller_word_separators, a
1193 # regex which defaults to standard word boundaries + digits + _
1194 #
1195 # Revision 1.101 2007/04/02 14:31:35 ncq
1196 # - cleanup
1197 #
1198 # Revision 1.100 2007/04/01 16:33:47 ncq
1199 # - try another parent for the MacOSX popup window
1200 #
1201 # Revision 1.99 2007/03/31 20:09:06 ncq
1202 # - make enchant optional
1203 #
1204 # Revision 1.98 2007/03/27 10:29:49 ncq
1205 # - better placement for default word list
1206 #
1207 # Revision 1.97 2007/03/27 09:59:26 ncq
1208 # - enable_default_spellchecker()
1209 #
1210 # Revision 1.96 2007/02/16 10:22:09 ncq
1211 # - _calc_display_string -> _picklist_selection2display_string to better reflect its use
1212 #
1213 # Revision 1.95 2007/02/06 13:45:39 ncq
1214 # - much improved docs
1215 # - remove aDelay from __init__ and make it a class variable
1216 # - thereby we can now dynamically adjust it at runtime :-)
1217 # - add patient searcher phrasewheel example
1218 #
1219 # Revision 1.94 2007/02/05 12:11:17 ncq
1220 # - put GPL into __license__
1221 # - code and layout cleanup
1222 # - remove dependancy on gmLog
1223 # - cleanup __init__ interface:
1224 # - remove selection_only
1225 # - remove aMatchProvider
1226 # - set both directly on instance members now
1227 # - implement spell checking plus test case for it
1228 # - implement configurable error messages
1229 #
1230 # Revision 1.93 2007/02/04 18:50:12 ncq
1231 # - capitalisation_mode is now instance variable
1232 #
1233 # Revision 1.92 2007/02/04 16:04:03 ncq
1234 # - reduce imports
1235 # - add accepted_chars constants
1236 # - enhance phrasewheel:
1237 # - better credits
1238 # - cleaner __init__ signature
1239 # - user properties
1240 # - code layout/naming cleanup
1241 # - no more snap_to_first_match for now
1242 # - add capitalisation mode
1243 # - add accepted chars checking
1244 # - add final regex matching
1245 # - allow suppressing recursive _on_text_update()
1246 # - always use time, even in slave mode
1247 # - lots of logic consolidation
1248 # - add SetText() and favour it over SetValue()
1249 #
1250 # Revision 1.91 2007/01/20 22:52:27 ncq
1251 # - .KeyCode -> GetKeyCode()
1252 #
1253 # Revision 1.90 2007/01/18 22:07:52 ncq
1254 # - (Get)KeyCode() -> KeyCode so 2.8 can do
1255 #
1256 # Revision 1.89 2007/01/06 23:44:19 ncq
1257 # - explicitely unset selection on lose focus
1258 #
1259 # Revision 1.88 2006/11/28 20:51:13 ncq
1260 # - a missing self
1261 # - remove some prints
1262 #
1263 # Revision 1.87 2006/11/27 23:08:36 ncq
1264 # - add snap_to_first_match
1265 # - add on_modified callbacks
1266 # - set background in lose_focus in some cases
1267 # - improve test suite
1268 #
1269 # Revision 1.86 2006/11/27 12:42:31 ncq
1270 # - somewhat improved dropdown picklist on Mac, not properly positioned yet
1271 #
1272 # Revision 1.85 2006/11/26 21:42:47 ncq
1273 # - don't use wx.ScrolledWindow or we suffer double-scrollers
1274 #
1275 # Revision 1.84 2006/11/26 20:58:20 ncq
1276 # - try working around lacking wx.PopupWindow
1277 #
1278 # Revision 1.83 2006/11/26 14:51:19 ncq
1279 # - cleanup/improve test suite so we can get MacOSX nailed (down)
1280 #
1281 # Revision 1.82 2006/11/26 14:09:59 ncq
1282 # - fix sys.path when running standalone for test suite
1283 # - fix test suite
1284 #
1285 # Revision 1.81 2006/11/24 09:58:39 ncq
1286 # - cleanup
1287 # - make it really work when matcher is None
1288 #
1289 # Revision 1.80 2006/11/19 11:16:02 ncq
1290 # - remove self._input_was_selected
1291 #
1292 # Revision 1.79 2006/11/06 12:54:00 ncq
1293 # - we don't actually need self._input_was_selected thanks to self.data
1294 #
1295 # Revision 1.78 2006/11/05 16:10:11 ncq
1296 # - cleanup
1297 # - now really handle context
1298 # - add unset_context()
1299 # - stop timer in __init__()
1300 # - start timer in _on_set_focus()
1301 # - some u''-ification
1302 #
1303 # Revision 1.77 2006/10/25 07:24:51 ncq
1304 # - gmPG -> gmPG2
1305 # - match provider _SQL deprecated
1306 #
1307 # Revision 1.76 2006/07/19 20:29:50 ncq
1308 # - import cleanup
1309 #
1310 # Revision 1.75 2006/07/04 14:15:17 ncq
1311 # - lots of cleanup
1312 # - make dropdown list scroll ! :-)
1313 # - add customized list control
1314 # - don't make dropdown go below screen height
1315 #
1316 # Revision 1.74 2006/07/01 15:14:26 ncq
1317 # - lots of cleanup
1318 # - simple border around list dropdown
1319 # - remove on_resize handling
1320 # - remove setdependant()
1321 # - handle down-arrow to drop down list
1322 #
1323 # Revision 1.73 2006/07/01 13:14:50 ncq
1324 # - cleanup as gleaned from TextCtrlAutoComplete
1325 #
1326 # Revision 1.72 2006/06/28 22:16:08 ncq
1327 # - add SetData() -- which only works if data can be found in the match space
1328 #
1329 # Revision 1.71 2006/06/18 13:47:29 ncq
1330 # - set self.input_was_selected=True if SetValue() does have data with it
1331 #
1332 # Revision 1.70 2006/06/05 21:36:40 ncq
1333 # - cleanup
1334 #
1335 # Revision 1.69 2006/06/02 09:59:03 ncq
1336 # - must invalidate associated data object *as soon as*
1337 # the text in the control changes
1338 #
1339 # Revision 1.68 2006/05/31 10:28:27 ncq
1340 # - cleanup
1341 # - deprecation warning for <id_callback> argument
1342 #
1343 # Revision 1.67 2006/05/25 22:24:20 ncq
1344 # - self.__input_was_selected -> self._input_was_selected
1345 # because subclasses need access to it
1346 #
1347 # Revision 1.66 2006/05/24 09:47:34 ncq
1348 # - remove superfluous self._is_modified, use MarkDirty() instead
1349 # - cleanup SetValue()
1350 # - client data in picklist better be object, not string
1351 # - add _calc_display_string() for better reuse in subclasses
1352 # - fix "pick list windows too small if one match" at the cost of extra
1353 # empty row when no horizontal scrollbar needed ...
1354 #
1355 # Revision 1.65 2006/05/20 18:54:15 ncq
1356 # - cleanup
1357 #
1358 # Revision 1.64 2006/05/01 18:49:49 ncq
1359 # - add_callback_on_set_focus()
1360 #
1361 # Revision 1.63 2005/10/09 08:15:21 ihaywood
1362 # SetValue () has optional second parameter to set data.
1363 #
1364 # Revision 1.62 2005/10/09 02:19:40 ihaywood
1365 # the address widget now has the appropriate widget order and behaviour for australia
1366 # when os.environ["LANG"] == 'en_AU' (is their a more graceful way of doing this?)
1367 #
1368 # Remember our postcodes work very differently.
1369 #
1370 # Revision 1.61 2005/10/04 00:04:45 sjtan
1371 # convert to wx.; catch some transitional errors temporarily
1372 #
1373 # Revision 1.60 2005/09/28 21:27:30 ncq
1374 # - a lot of wx2.6-ification
1375 #
1376 # Revision 1.59 2005/09/28 15:57:48 ncq
1377 # - a whole bunch of wx.Foo -> wx.Foo
1378 #
1379 # Revision 1.58 2005/09/26 18:01:51 ncq
1380 # - use proper way to import wx26 vs wx2.4
1381 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES
1382 # - time for fixup
1383 #
1384 # Revision 1.57 2005/08/14 15:37:36 ncq
1385 # - cleanup
1386 #
1387 # Revision 1.56 2005/07/24 11:35:59 ncq
1388 # - use robustified gmTimer.Start() interface
1389 #
1390 # Revision 1.55 2005/07/23 21:55:40 shilbert
1391 # *** empty log message ***
1392 #
1393 # Revision 1.54 2005/07/23 21:10:58 ncq
1394 # - explicitely use milliseconds=-1 in timer.Start()
1395 #
1396 # Revision 1.53 2005/07/23 19:24:58 ncq
1397 # - debug timer start() on windows
1398 #
1399 # Revision 1.52 2005/07/04 11:20:59 ncq
1400 # - cleanup cruft
1401 # - on_set_focus() set value to first match if previously empty
1402 # - on_lose_focus() set value if selection_only and only one match and not yet selected
1403 #
1404 # Revision 1.51 2005/06/14 19:55:37 cfmoro
1405 # Set selection flag when setting value
1406 #
1407 # Revision 1.50 2005/06/07 10:18:23 ncq
1408 # - cleanup
1409 # - setContext -> set_context
1410 #
1411 # Revision 1.49 2005/06/01 23:09:02 ncq
1412 # - set default phrasewheel delay to 150ms
1413 #
1414 # Revision 1.48 2005/05/23 16:42:50 ncq
1415 # - when we SetValue(val) we need to only check those matches
1416 # that actually *can* match, eg the output of getMatches(val)
1417 #
1418 # Revision 1.47 2005/05/22 23:09:13 cfmoro
1419 # Adjust the underlying data when setting the phrasewheel value
1420 #
1421 # Revision 1.46 2005/05/17 08:06:38 ncq
1422 # - support for callbacks on lost focus
1423 #
1424 # Revision 1.45 2005/05/14 15:06:48 ncq
1425 # - GetData()
1426 #
1427 # Revision 1.44 2005/05/05 06:31:06 ncq
1428 # - remove dead cWheelTimer code in favour of gmTimer.py
1429 # - add self._on_enter_callbacks and add_callback_on_enter()
1430 # - addCallback() -> add_callback_on_selection()
1431 #
1432 # Revision 1.43 2005/03/14 14:37:56 ncq
1433 # - only disable timer if slave mode is really active
1434 #
1435 # Revision 1.42 2004/12/27 16:23:39 ncq
1436 # - gmTimer callbacks take a cookie
1437 #
1438 # Revision 1.41 2004/12/23 16:21:21 ncq
1439 # - some cleanup
1440 #
1441 # Revision 1.40 2004/10/16 22:42:12 sjtan
1442 #
1443 # script for unitesting; guard for unit tests where unit uses gmPhraseWheel; fixup where version of wxPython doesn't allow
1444 # a child widget to be multiply inserted (gmDemographics) ; try block for later versions of wxWidgets that might fail
1445 # the Add (.. w,h, ... ) because expecting Add(.. (w,h) ...)
1446 #
1447 # Revision 1.39 2004/09/13 09:24:30 ncq
1448 # - don't start timers in slave_mode since cannot start from
1449 # other than main thread, this is a dirty fix but will do for now
1450 #
1451 # Revision 1.38 2004/06/25 12:30:52 ncq
1452 # - use True/False
1453 #
1454 # Revision 1.37 2004/06/17 11:43:15 ihaywood
1455 # Some minor bugfixes.
1456 # My first experiments with wxGlade
1457 # changed gmPhraseWheel so the match provider can be added after instantiation
1458 # (as wxGlade can't do this itself)
1459 #
1460 # Revision 1.36 2004/05/02 22:53:53 ncq
1461 # - cleanup
1462 #
1463 # Revision 1.35 2004/05/01 10:27:47 shilbert
1464 # - self._picklist.Append() needs string or unicode object
1465 #
1466 # Revision 1.34 2004/03/05 11:22:35 ncq
1467 # - import from Gnumed.<pkg>
1468 #
1469 # Revision 1.33 2004/03/02 10:21:10 ihaywood
1470 # gmDemographics now supports comm channels, occupation,
1471 # country of birth and martial status
1472 #
1473 # Revision 1.32 2004/02/25 09:46:22 ncq
1474 # - import from pycommon now, not python-common
1475 #
1476 # Revision 1.31 2004/01/12 13:14:39 ncq
1477 # - remove dead code
1478 # - correctly calculate new pick list position: don't go to TOPLEVEL
1479 # window but rather to immediate parent ...
1480 #
1481 # Revision 1.30 2004/01/06 10:06:02 ncq
1482 # - make SQL based phrase wheel test work again
1483 #
1484 # Revision 1.29 2003/11/19 23:42:00 ncq
1485 # - cleanup, comment out snap()
1486 #
1487 # Revision 1.28 2003/11/18 23:17:47 ncq
1488 # - cleanup, fixed variable names
1489 #
1490 # Revision 1.27 2003/11/17 10:56:38 sjtan
1491 #
1492 # synced and commiting.
1493 #
1494 # Revision 1.26 2003/11/09 14:28:30 ncq
1495 # - cleanup
1496 #
1497 # Revision 1.25 2003/11/09 02:24:42 ncq
1498 # - added Syans "input was selected from list" state flag to avoid unnecessary list
1499 # drop downs
1500 # - variable name cleanup
1501 #
1502 # Revision 1.24 2003/11/07 20:48:04 ncq
1503 # - place comments where they belong
1504 #
1505 # Revision 1.23 2003/11/05 22:21:06 sjtan
1506 #
1507 # let's gmDateInput specify id_callback in constructor list.
1508 #
1509 # Revision 1.22 2003/11/04 10:35:23 ihaywood
1510 # match providers in gmDemographicRecord
1511 #
1512 # Revision 1.21 2003/11/04 01:40:27 ihaywood
1513 # match providers moved to python-common
1514 #
1515 # Revision 1.20 2003/10/26 11:27:10 ihaywood
1516 # gmPatient is now the "patient stub", all demographics stuff in gmDemographics.
1517 #
1518 # Ergregious breakages are fixed, but needs more work
1519 #
1520 # Revision 1.19 2003/10/09 15:45:16 ncq
1521 # - validate cookie column in score tables, too
1522 #
1523 # Revision 1.18 2003/10/07 22:20:50 ncq
1524 # - ported Syan's extra_sql_condition extension
1525 # - make SQL match provider aware of separate scoring tables
1526 #
1527 # Revision 1.17 2003/10/03 00:20:25 ncq
1528 # - handle case where matches = 1 and match = input -> don't show picklist
1529 #
1530 # Revision 1.16 2003/10/02 20:51:12 ncq
1531 # - add alt-XX shortcuts, move __* to _*
1532 #
1533 # Revision 1.15 2003/09/30 18:52:40 ncq
1534 # - factored out date input wheel
1535 #
1536 # Revision 1.14 2003/09/29 23:11:58 ncq
1537 # - add __explicit_offset() date expander
1538 #
1539 # Revision 1.13 2003/09/29 00:16:55 ncq
1540 # - added date match provider
1541 #
1542 # Revision 1.12 2003/09/21 10:55:04 ncq
1543 # - coalesce merge conflicts due to optional SQL phrase wheel testing
1544 #
1545 # Revision 1.11 2003/09/21 07:52:57 ihaywood
1546 # those bloody umlauts killed by python interpreter!
1547 #
1548 # Revision 1.10 2003/09/17 05:54:32 ihaywood
1549 # phrasewheel box size now approximate to length of search results
1550 #
1551 # Revision 1.8 2003/09/16 22:25:45 ncq
1552 # - cleanup
1553 # - added first draft of single-column-per-table SQL match provider
1554 # - added module test for SQL matcher
1555 #
1556 # Revision 1.7 2003/09/15 16:05:30 ncq
1557 # - allow several phrases to be typed in and only try to match
1558 # the one the cursor is in at the moment
1559 #
1560 # Revision 1.6 2003/09/13 17:46:29 ncq
1561 # - pattern match word separators
1562 # - pattern match ignore characters as per Richard's suggestion
1563 # - start work on phrase separator pattern matching with extraction of
1564 # relevant input part (where the cursor is at currently)
1565 #
1566 # Revision 1.5 2003/09/10 01:50:25 ncq
1567 # - cleanup
1568 #
1569 #
1570
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:02:29 2010 | http://epydoc.sourceforge.net |