Package Gnumed :: Package wxpython :: Module gmPhraseWheel
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmPhraseWheel

   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  __author__  = "K.Hilbert <Karsten.Hilbert@gmx.net>, I.Haywood, S.J.Tan <sjtan@bigpond.com>" 
  11  __license__ = "GPL" 
  12   
  13  # stdlib 
  14  import string, types, time, sys, re as regex, os.path 
  15   
  16   
  17  # 3rd party 
  18  import wx 
  19  import wx.lib.mixins.listctrl as listmixins 
  20   
  21   
  22  # GNUmed specific 
  23  if __name__ == '__main__': 
  24          sys.path.insert(0, '../../') 
  25  from Gnumed.pycommon import gmTools 
  26  from Gnumed.pycommon import gmDispatcher 
  27   
  28   
  29  import logging 
  30  _log = logging.getLogger('macosx') 
  31   
  32   
  33  color_prw_invalid = 'pink' 
  34  color_prw_partially_invalid = 'yellow' 
  35  color_prw_valid = None                          # this is used by code outside this module 
  36   
  37  #default_phrase_separators = r'[;/|]+' 
  38  default_phrase_separators = r';+' 
  39  default_spelling_word_separators = r'[\W\d_]+' 
  40   
  41  # those can be used by the <accepted_chars> phrasewheel parameter 
  42  NUMERIC = '0-9' 
  43  ALPHANUMERIC = 'a-zA-Z0-9' 
  44  EMAIL_CHARS = "a-zA-Z0-9\-_@\." 
  45  WEB_CHARS = "a-zA-Z0-9\.\-_/:" 
  46   
  47   
  48  _timers = [] 
  49  #============================================================ 
50 -def shutdown():
51 """It can be useful to call this early from your shutdown code to avoid hangs on Notify().""" 52 global _timers 53 _log.info('shutting down %s pending timers', len(_timers)) 54 for timer in _timers: 55 _log.debug('timer [%s]', timer) 56 timer.Stop() 57 _timers = []
58 #------------------------------------------------------------
59 -class _cPRWTimer(wx.Timer):
60
61 - def __init__(self, *args, **kwargs):
62 wx.Timer.__init__(self, *args, **kwargs) 63 self.callback = lambda x:x 64 global _timers 65 _timers.append(self)
66
67 - def Notify(self):
68 self.callback()
69 #============================================================ 70 # FIXME: merge with gmListWidgets
71 -class cPhraseWheelListCtrl(wx.ListCtrl, listmixins.ListCtrlAutoWidthMixin):
72
73 - def __init__(self, *args, **kwargs):
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 #--------------------------------------------------------
80 - def SetItems(self, items):
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['list_label'])
86 #--------------------------------------------------------
87 - def GetSelectedItemData(self):
88 sel_idx = self.GetFirstSelected() 89 if sel_idx == -1: 90 return None 91 return self.__data[sel_idx]['data']
92 #--------------------------------------------------------
93 - def get_selected_item(self):
94 sel_idx = self.GetFirstSelected() 95 if sel_idx == -1: 96 return None 97 return self.__data[sel_idx]
98 #--------------------------------------------------------
99 - def get_selected_item_label(self):
100 sel_idx = self.GetFirstSelected() 101 if sel_idx == -1: 102 return None 103 return self.__data[sel_idx]['list_label']
104 #============================================================ 105 # base class for both single- and multi-phrase phrase wheels 106 #------------------------------------------------------------
107 -class cPhraseWheelBase(wx.TextCtrl):
108 """Widget for smart guessing of user fields, after Richard Terry's interface. 109 110 - VB implementation by Richard Terry 111 - Python port by Ian Haywood for GNUmed 112 - enhanced by Karsten Hilbert for GNUmed 113 - enhanced by Ian Haywood for aumed 114 - enhanced by Karsten Hilbert for GNUmed 115 116 @param matcher: a class used to find matches for the current input 117 @type matcher: a L{match provider<Gnumed.pycommon.gmMatchProvider.cMatchProvider>} 118 instance or C{None} 119 120 @param selection_only: whether free-text can be entered without associated data 121 @type selection_only: boolean 122 123 @param capitalisation_mode: how to auto-capitalize input, valid values 124 are found in L{capitalize()<Gnumed.pycommon.gmTools.capitalize>} 125 @type capitalisation_mode: integer 126 127 @param accepted_chars: a regex pattern defining the characters 128 acceptable in the input string, if None no checking is performed 129 @type accepted_chars: None or a string holding a valid regex pattern 130 131 @param final_regex: when the control loses focus the input is 132 checked against this regular expression 133 @type final_regex: a string holding a valid regex pattern 134 135 @param navigate_after_selection: whether or not to immediately 136 navigate to the widget next-in-tab-order after selecting an 137 item from the dropdown picklist 138 @type navigate_after_selection: boolean 139 140 @param speller: if not None used to spellcheck the current input 141 and to retrieve suggested replacements/completions 142 @type speller: None or a L{enchant Dict<enchant>} descendant 143 144 @param picklist_delay: this much time of user inactivity must have 145 passed before the input related smarts kick in and the drop 146 down pick list is shown 147 @type picklist_delay: integer (milliseconds) 148 """
149 - def __init__ (self, parent=None, id=-1, *args, **kwargs):
150 151 # behaviour 152 self.matcher = None 153 self.selection_only = False 154 self.selection_only_error_msg = _('You must select a value from the picklist or type an exact match.') 155 self.capitalisation_mode = gmTools.CAPS_NONE 156 self.accepted_chars = None 157 self.final_regex = '.*' 158 self.final_regex_error_msg = _('The content is invalid. It must match the regular expression: [%%s]. <%s>') % self.__class__.__name__ 159 self.navigate_after_selection = False 160 self.speller = None 161 self.speller_word_separators = default_spelling_word_separators 162 self.picklist_delay = 150 # milliseconds 163 164 # state tracking 165 self._has_focus = False 166 self._current_match_candidates = [] 167 self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y) 168 self.suppress_text_update_smarts = False 169 170 self.__static_tt = None 171 self.__static_tt_extra = None 172 # don't do this or the tooltip code will fail: self.data = {} 173 # do this instead: 174 self._data = {} 175 176 self._on_selection_callbacks = [] 177 self._on_lose_focus_callbacks = [] 178 self._on_set_focus_callbacks = [] 179 self._on_modified_callbacks = [] 180 181 try: 182 kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_TAB | wx.TE_PROCESS_ENTER 183 except KeyError: 184 kwargs['style'] = wx.TE_PROCESS_TAB | wx.TE_PROCESS_ENTER 185 super(cPhraseWheelBase, self).__init__(parent, id, **kwargs) 186 187 self.__my_startup_color = self.GetBackgroundColour() 188 self.__non_edit_font = self.GetFont() 189 global color_prw_valid 190 if color_prw_valid is None: 191 color_prw_valid = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) 192 193 self.__init_dropdown(parent = parent) 194 self.__register_events() 195 self.__init_timer()
196 #-------------------------------------------------------- 197 # external API 198 #---------------------------------------------------------
199 - def GetData(self, can_create=False):
200 """Retrieve the data associated with the displayed string(s). 201 202 - self._create_data() must set self.data if possible (/successful) 203 """ 204 if len(self._data) == 0: 205 if can_create: 206 self._create_data() 207 208 return self._data
209 #---------------------------------------------------------
210 - def SetText(self, value=u'', data=None, suppress_smarts=False):
211 212 if value is None: 213 value = u'' 214 215 self.suppress_text_update_smarts = suppress_smarts 216 217 if data is not None: 218 self.suppress_text_update_smarts = True 219 self.data = self._dictify_data(data = data, value = value) 220 super(cPhraseWheelBase, self).SetValue(value) 221 self.display_as_valid(valid = True) 222 223 # if data already available 224 if len(self._data) > 0: 225 return True 226 227 # empty text value ? 228 if value == u'': 229 # valid value not required ? 230 if not self.selection_only: 231 return True 232 233 if not self._set_data_to_first_match(): 234 # not found 235 if self.selection_only: 236 self.display_as_valid(valid = False) 237 return False 238 239 return True
240 #--------------------------------------------------------
241 - def set_from_instance(self, instance):
242 raise NotImplementedError('[%s]: set_from_instance()' % self.__class__.__name__)
243 #--------------------------------------------------------
244 - def set_from_pk(self, pk):
245 raise NotImplementedError('[%s]: set_from_pk()' % self.__class__.__name__)
246 #--------------------------------------------------------
247 - def display_as_valid(self, valid=None, partially_invalid=False):
248 if valid is True: 249 self.SetBackgroundColour(self.__my_startup_color) 250 elif valid is False: 251 if partially_invalid: 252 self.SetBackgroundColour(color_prw_partially_invalid) 253 else: 254 self.SetBackgroundColour(color_prw_invalid) 255 else: 256 raise ValueError(u'<valid> must be True or False') 257 self.Refresh()
258 #--------------------------------------------------------
259 - def display_as_disabled(self, disabled=None):
260 if disabled is True: 261 self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BACKGROUND)) 262 elif disabled is False: 263 self.SetBackgroundColour(color_prw_valid) 264 else: 265 raise ValueError(u'<disabled> must be True or False') 266 self.Refresh()
267 #-------------------------------------------------------- 268 # callback API 269 #--------------------------------------------------------
270 - def add_callback_on_selection(self, callback=None):
271 """Add a callback for invocation when a picklist item is selected. 272 273 The callback will be invoked whenever an item is selected 274 from the picklist. The associated data is passed in as 275 a single parameter. Callbacks must be able to cope with 276 None as the data parameter as that is sent whenever the 277 user changes a previously selected value. 278 """ 279 if not callable(callback): 280 raise ValueError('[add_callback_on_selection]: ignoring callback [%s], it is not callable' % callback) 281 282 self._on_selection_callbacks.append(callback)
283 #---------------------------------------------------------
284 - def add_callback_on_set_focus(self, callback=None):
285 """Add a callback for invocation when getting focus.""" 286 if not callable(callback): 287 raise ValueError('[add_callback_on_set_focus]: ignoring callback [%s] - not callable' % callback) 288 289 self._on_set_focus_callbacks.append(callback)
290 #---------------------------------------------------------
291 - def add_callback_on_lose_focus(self, callback=None):
292 """Add a callback for invocation when losing focus.""" 293 if not callable(callback): 294 raise ValueError('[add_callback_on_lose_focus]: ignoring callback [%s] - not callable' % callback) 295 296 self._on_lose_focus_callbacks.append(callback)
297 #---------------------------------------------------------
298 - def add_callback_on_modified(self, callback=None):
299 """Add a callback for invocation when the content is modified. 300 301 This callback will NOT be passed any values. 302 """ 303 if not callable(callback): 304 raise ValueError('[add_callback_on_modified]: ignoring callback [%s] - not callable' % callback) 305 306 self._on_modified_callbacks.append(callback)
307 #-------------------------------------------------------- 308 # match provider proxies 309 #--------------------------------------------------------
310 - def set_context(self, context=None, val=None):
311 if self.matcher is not None: 312 self.matcher.set_context(context=context, val=val)
313 #---------------------------------------------------------
314 - def unset_context(self, context=None):
315 if self.matcher is not None: 316 self.matcher.unset_context(context=context)
317 #-------------------------------------------------------- 318 # spell-checking 319 #--------------------------------------------------------
321 # FIXME: use Debian's wgerman-medical as "personal" wordlist if available 322 try: 323 import enchant 324 except ImportError: 325 self.speller = None 326 return False 327 328 try: 329 self.speller = enchant.DictWithPWL(None, os.path.expanduser(os.path.join('~', '.gnumed', 'spellcheck', 'wordlist.pwl'))) 330 except enchant.DictNotFoundError: 331 self.speller = None 332 return False 333 334 return True
335 #---------------------------------------------------------
337 if self.speller is None: 338 return None 339 340 # get the last word 341 last_word = self.__speller_word_separators.split(val)[-1] 342 if last_word.strip() == u'': 343 return None 344 345 try: 346 suggestions = self.speller.suggest(last_word) 347 except: 348 _log.exception('had to disable (enchant) spell checker') 349 self.speller = None 350 return None 351 352 if len(suggestions) == 0: 353 return None 354 355 input2match_without_last_word = val[:val.rindex(last_word)] 356 return [ input2match_without_last_word + suggestion for suggestion in suggestions ]
357 #--------------------------------------------------------
358 - def _set_speller_word_separators(self, word_separators):
359 if word_separators is None: 360 self.__speller_word_separators = regex.compile(default_spelling_word_separators, flags = regex.LOCALE | regex.UNICODE) 361 else: 362 self.__speller_word_separators = regex.compile(word_separators, flags = regex.LOCALE | regex.UNICODE)
363
365 return self.__speller_word_separators.pattern
366 367 speller_word_separators = property(_get_speller_word_separators, _set_speller_word_separators) 368 #-------------------------------------------------------- 369 # internal API 370 #-------------------------------------------------------- 371 # picklist handling 372 #--------------------------------------------------------
373 - def __init_dropdown(self, parent = None):
374 szr_dropdown = None 375 try: 376 #raise NotImplementedError # uncomment for testing 377 self.__dropdown_needs_relative_position = False 378 self._picklist_dropdown = wx.PopupWindow(parent) 379 list_parent = self._picklist_dropdown 380 self.__use_fake_popup = False 381 except NotImplementedError: 382 self.__use_fake_popup = True 383 384 # on MacOSX wx.PopupWindow is not implemented, so emulate it 385 add_picklist_to_sizer = True 386 szr_dropdown = wx.BoxSizer(wx.VERTICAL) 387 388 # using wx.MiniFrame 389 self.__dropdown_needs_relative_position = False 390 self._picklist_dropdown = wx.MiniFrame ( 391 parent = parent, 392 id = -1, 393 style = wx.SIMPLE_BORDER | wx.FRAME_FLOAT_ON_PARENT | wx.FRAME_NO_TASKBAR | wx.POPUP_WINDOW 394 ) 395 scroll_win = wx.ScrolledWindow(parent = self._picklist_dropdown, style = wx.NO_BORDER) 396 scroll_win.SetSizer(szr_dropdown) 397 list_parent = scroll_win 398 399 # using wx.Window 400 #self.__dropdown_needs_relative_position = True 401 #self._picklist_dropdown = wx.ScrolledWindow(parent=parent, style = wx.RAISED_BORDER) 402 #self._picklist_dropdown.SetSizer(szr_dropdown) 403 #list_parent = self._picklist_dropdown 404 405 self.__mac_log('dropdown parent: %s' % self._picklist_dropdown.GetParent()) 406 407 self._picklist = cPhraseWheelListCtrl ( 408 list_parent, 409 style = wx.LC_NO_HEADER 410 ) 411 self._picklist.InsertColumn(0, u'') 412 413 if szr_dropdown is not None: 414 szr_dropdown.Add(self._picklist, 1, wx.EXPAND) 415 416 self._picklist_dropdown.Hide()
417 #--------------------------------------------------------
418 - def _show_picklist(self, input2match):
419 """Display the pick list if useful.""" 420 421 self._picklist_dropdown.Hide() 422 423 if not self._has_focus: 424 return 425 426 if len(self._current_match_candidates) == 0: 427 return 428 429 # if only one match and text == match: do not show 430 # picklist but rather pick that match 431 if len(self._current_match_candidates) == 1: 432 candidate = self._current_match_candidates[0] 433 if candidate['field_label'] == input2match: 434 self._update_data_from_picked_item(candidate) 435 return 436 437 # recalculate size 438 dropdown_size = self._picklist_dropdown.GetSize() 439 border_width = 4 440 extra_height = 25 441 # height 442 rows = len(self._current_match_candidates) 443 if rows < 2: # 2 rows minimum 444 rows = 2 445 if rows > 20: # 20 rows maximum 446 rows = 20 447 self.__mac_log('dropdown needs rows: %s' % rows) 448 pw_size = self.GetSize() 449 dropdown_size.SetHeight ( 450 (pw_size.height * rows) 451 + border_width 452 + extra_height 453 ) 454 # width 455 dropdown_size.SetWidth(min ( 456 self.Size.width * 2, 457 self.Parent.Size.width 458 )) 459 460 # recalculate position 461 (pw_x_abs, pw_y_abs) = self.ClientToScreenXY(0,0) 462 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))) 463 dropdown_new_x = pw_x_abs 464 dropdown_new_y = pw_y_abs + pw_size.height 465 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))) 466 self.__mac_log('desired dropdown size: %s' % dropdown_size) 467 468 # reaches beyond screen ? 469 if (dropdown_new_y + dropdown_size.height) > self._screenheight: 470 self.__mac_log('dropdown extends offscreen (screen max y: %s)' % self._screenheight) 471 max_height = self._screenheight - dropdown_new_y - 4 472 self.__mac_log('max dropdown height would be: %s' % max_height) 473 if max_height > ((pw_size.height * 2) + 4): 474 dropdown_size.SetHeight(max_height) 475 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))) 476 self.__mac_log('possible dropdown size: %s' % dropdown_size) 477 478 # now set dimensions 479 self._picklist_dropdown.SetSize(dropdown_size) 480 self._picklist.SetSize(self._picklist_dropdown.GetClientSize()) 481 self.__mac_log('pick list size set to: %s' % self._picklist_dropdown.GetSize()) 482 if self.__dropdown_needs_relative_position: 483 dropdown_new_x, dropdown_new_y = self._picklist_dropdown.GetParent().ScreenToClientXY(dropdown_new_x, dropdown_new_y) 484 self._picklist_dropdown.MoveXY(dropdown_new_x, dropdown_new_y) 485 486 # select first value 487 self._picklist.Select(0) 488 489 # and show it 490 self._picklist_dropdown.Show(True)
491 492 # dropdown_top_left = self._picklist_dropdown.ClientToScreenXY(0,0) 493 # dropdown_size = self._picklist_dropdown.GetSize() 494 # dropdown_bottom_right = self._picklist_dropdown.ClientToScreenXY(dropdown_size.width, dropdown_size.height) 495 # self.__mac_log('dropdown placement now (on screen): x:%s-%s, y:%s-%s' % ( 496 # dropdown_top_left[0], 497 # dropdown_bottom_right[0], 498 # dropdown_top_left[1], 499 # dropdown_bottom_right[1]) 500 # ) 501 #--------------------------------------------------------
502 - def _hide_picklist(self):
503 """Hide the pick list.""" 504 self._picklist_dropdown.Hide()
505 #--------------------------------------------------------
506 - def _select_picklist_row(self, new_row_idx=None, old_row_idx=None):
507 """Mark the given picklist row as selected.""" 508 if old_row_idx is not None: 509 pass # FIXME: do we need unselect here ? Select() should do it for us 510 self._picklist.Select(new_row_idx) 511 self._picklist.EnsureVisible(new_row_idx)
512 #--------------------------------------------------------
513 - def _picklist_item2display_string(self, item=None):
514 """Get string to display in the field for the given picklist item.""" 515 if item is None: 516 item = self._picklist.get_selected_item() 517 try: 518 return item['field_label'] 519 except KeyError: 520 pass 521 try: 522 return item['list_label'] 523 except KeyError: 524 pass 525 try: 526 return item['label'] 527 except KeyError: 528 return u'<no field_*/list_*/label in item>'
529 #return self._picklist.GetItemText(self._picklist.GetFirstSelected()) 530 #--------------------------------------------------------
531 - def _update_display_from_picked_item(self, item):
532 """Update the display to show item strings.""" 533 # default to single phrase 534 display_string = self._picklist_item2display_string(item = item) 535 self.suppress_text_update_smarts = True 536 super(cPhraseWheelBase, self).SetValue(display_string) 537 # in single-phrase phrasewheels always set cursor to end of string 538 self.SetInsertionPoint(self.GetLastPosition()) 539 return
540 #-------------------------------------------------------- 541 # match generation 542 #--------------------------------------------------------
544 raise NotImplementedError('[%s]: fragment extraction not implemented' % self.__class__.__name__)
545 #---------------------------------------------------------
546 - def _update_candidates_in_picklist(self, val):
547 """Get candidates matching the currently typed input.""" 548 549 # get all currently matching items 550 self._current_match_candidates = [] 551 if self.matcher is not None: 552 matched, self._current_match_candidates = self.matcher.getMatches(val) 553 self._picklist.SetItems(self._current_match_candidates) 554 555 # no matches: 556 # - none found (perhaps due to a typo) 557 # - or no matcher available 558 # anyway: spellcheck 559 if len(self._current_match_candidates) == 0: 560 suggestions = self._get_suggestions_from_spell_checker(val) 561 if suggestions is not None: 562 self._current_match_candidates = [ 563 {'list_label': suggestion, 'field_label': suggestion, 'data': None} 564 for suggestion in suggestions 565 ] 566 self._picklist.SetItems(self._current_match_candidates)
567 #-------------------------------------------------------- 568 # tooltip handling 569 #--------------------------------------------------------
570 - def _get_data_tooltip(self):
571 # child classes can override this to provide 572 # per data item dynamic tooltips, 573 # by default do not support dynamic tooltip parts: 574 return None
575 #--------------------------------------------------------
576 - def __recalculate_tooltip(self):
577 """Calculate dynamic tooltip part based on data item. 578 579 - called via ._set_data() each time property .data (-> .__data) is set 580 - hence also called the first time data is set 581 - the static tooltip can be set any number of ways before that 582 - only when data is first set does the dynamic part become relevant 583 - hence it is sufficient to remember the static part when .data is 584 set for the first time 585 """ 586 if self.__static_tt is None: 587 if self.ToolTip is None: 588 self.__static_tt = u'' 589 else: 590 self.__static_tt = self.ToolTip.Tip 591 592 # need to always calculate static part because 593 # the dynamic part can have *become* None, again, 594 # in which case we want to be able to re-set the 595 # tooltip to the static part 596 static_part = self.__static_tt 597 if (self.__static_tt_extra) is not None and (self.__static_tt_extra.strip() != u''): 598 static_part = u'%s\n\n%s' % ( 599 static_part, 600 self.__static_tt_extra 601 ) 602 603 dynamic_part = self._get_data_tooltip() 604 if dynamic_part is None: 605 self.SetToolTipString(static_part) 606 return 607 608 if static_part == u'': 609 tt = dynamic_part 610 else: 611 if dynamic_part.strip() == u'': 612 tt = static_part 613 else: 614 tt = u'%s\n\n%s\n\n%s' % ( 615 dynamic_part, 616 gmTools.u_box_horiz_single * 32, 617 static_part 618 ) 619 620 self.SetToolTipString(tt)
621 #--------------------------------------------------------
622 - def _get_static_tt_extra(self):
623 return self.__static_tt_extra
624
625 - def _set_static_tt_extra(self, tt):
626 self.__static_tt_extra = tt
627 628 static_tooltip_extra = property(_get_static_tt_extra, _set_static_tt_extra) 629 #-------------------------------------------------------- 630 # event handling 631 #--------------------------------------------------------
632 - def __register_events(self):
633 wx.EVT_KEY_DOWN (self, self._on_key_down) 634 wx.EVT_SET_FOCUS(self, self._on_set_focus) 635 wx.EVT_KILL_FOCUS(self, self._on_lose_focus) 636 wx.EVT_TEXT(self, self.GetId(), self._on_text_update) 637 self._picklist.Bind(wx.EVT_LEFT_DCLICK, self._on_list_item_selected)
638 #--------------------------------------------------------
639 - def _on_key_down(self, event):
640 """Is called when a key is pressed.""" 641 642 keycode = event.GetKeyCode() 643 644 if keycode == wx.WXK_DOWN: 645 self.__on_cursor_down() 646 return 647 648 if keycode == wx.WXK_UP: 649 self.__on_cursor_up() 650 return 651 652 if keycode == wx.WXK_RETURN: 653 self._on_enter() 654 return 655 656 if keycode == wx.WXK_TAB: 657 if event.ShiftDown(): 658 self.Navigate(flags = wx.NavigationKeyEvent.IsBackward) 659 return 660 self.__on_tab() 661 self.Navigate(flags = wx.NavigationKeyEvent.IsForward) 662 return 663 664 # FIXME: need PAGE UP/DOWN//POS1/END here to move in picklist 665 if keycode in [wx.WXK_SHIFT, wx.WXK_BACK, wx.WXK_DELETE, wx.WXK_LEFT, wx.WXK_RIGHT]: 666 pass 667 668 # need to handle all non-character key presses *before* this check 669 elif not self.__char_is_allowed(char = unichr(event.GetUnicodeKey())): 670 wx.Bell() 671 # Richard doesn't show any error message here 672 return 673 674 event.Skip() 675 return
676 #--------------------------------------------------------
677 - def _on_set_focus(self, event):
678 679 self._has_focus = True 680 event.Skip() 681 682 self.__non_edit_font = self.GetFont() 683 edit_font = self.GetFont() 684 edit_font.SetPointSize(pointSize = self.__non_edit_font.GetPointSize() + 1) 685 self.SetFont(edit_font) 686 self.Refresh() 687 688 # notify interested parties 689 for callback in self._on_set_focus_callbacks: 690 callback() 691 692 self.__timer.Start(oneShot = True, milliseconds = self.picklist_delay) 693 return True
694 #--------------------------------------------------------
695 - def _on_lose_focus(self, event):
696 """Do stuff when leaving the control. 697 698 The user has had her say, so don't second guess 699 intentions but do report error conditions. 700 """ 701 event.Skip() 702 self._has_focus = False 703 self.__timer.Stop() 704 self._hide_picklist() 705 wx.CallAfter(self.__on_lost_focus) 706 return True
707 #--------------------------------------------------------
708 - def __on_lost_focus(self):
709 self.SetSelection(1,1) 710 self.SetFont(self.__non_edit_font) 711 #self.Refresh() # already done in .display_as_valid() below 712 713 is_valid = True 714 715 # the user may have typed a phrase that is an exact match, 716 # however, just typing it won't associate data from the 717 # picklist, so try do that now 718 self._set_data_to_first_match() 719 720 # check value against final_regex if any given 721 if self.__final_regex.match(self.GetValue().strip()) is None: 722 gmDispatcher.send(signal = 'statustext', msg = self.final_regex_error_msg) 723 is_valid = False 724 725 self.display_as_valid(valid = is_valid) 726 727 # notify interested parties 728 for callback in self._on_lose_focus_callbacks: 729 callback()
730 #--------------------------------------------------------
731 - def _on_list_item_selected(self, *args, **kwargs):
732 """Gets called when user selected a list item.""" 733 734 self._hide_picklist() 735 736 item = self._picklist.get_selected_item() 737 # huh ? 738 if item is None: 739 self.display_as_valid(valid = True) 740 return 741 742 self._update_display_from_picked_item(item) 743 self._update_data_from_picked_item(item) 744 self.MarkDirty() 745 746 # and tell the listeners about the user's selection 747 for callback in self._on_selection_callbacks: 748 callback(self._data) 749 750 if self.navigate_after_selection: 751 self.Navigate() 752 753 return
754 #--------------------------------------------------------
755 - def _on_text_update (self, event):
756 """Internal handler for wx.EVT_TEXT. 757 758 Called when text was changed by user or by SetValue(). 759 """ 760 if self.suppress_text_update_smarts: 761 self.suppress_text_update_smarts = False 762 return 763 764 self._adjust_data_after_text_update() 765 self._current_match_candidates = [] 766 767 val = self.GetValue().strip() 768 ins_point = self.GetInsertionPoint() 769 770 # if empty string then hide list dropdown window 771 # we also don't need a timer event then 772 if val == u'': 773 self._hide_picklist() 774 self.__timer.Stop() 775 else: 776 new_val = gmTools.capitalize(text = val, mode = self.capitalisation_mode) 777 if new_val != val: 778 self.suppress_text_update_smarts = True 779 super(cPhraseWheelBase, self).SetValue(new_val) 780 if ins_point > len(new_val): 781 self.SetInsertionPointEnd() 782 else: 783 self.SetInsertionPoint(ins_point) 784 # FIXME: SetSelection() ? 785 786 # start timer for delayed match retrieval 787 self.__timer.Start(oneShot = True, milliseconds = self.picklist_delay) 788 789 # notify interested parties 790 for callback in self._on_modified_callbacks: 791 callback() 792 793 return
794 #-------------------------------------------------------- 795 # keypress handling 796 #--------------------------------------------------------
797 - def _on_enter(self):
798 """Called when the user pressed <ENTER>.""" 799 if self._picklist_dropdown.IsShown(): 800 self._on_list_item_selected() 801 else: 802 # FIXME: check for errors before navigation 803 self.Navigate()
804 #--------------------------------------------------------
805 - def __on_cursor_down(self):
806 807 if self._picklist_dropdown.IsShown(): 808 idx_selected = self._picklist.GetFirstSelected() 809 if idx_selected < (len(self._current_match_candidates) - 1): 810 self._select_picklist_row(idx_selected + 1, idx_selected) 811 return 812 813 # if we don't yet have a pick list: open new pick list 814 # (this can happen when we TAB into a field pre-filled 815 # with the top-weighted contextual item but want to 816 # select another contextual item) 817 self.__timer.Stop() 818 if self.GetValue().strip() == u'': 819 val = u'*' 820 else: 821 val = self._extract_fragment_to_match_on() 822 self._update_candidates_in_picklist(val = val) 823 self._show_picklist(input2match = val)
824 #--------------------------------------------------------
825 - def __on_cursor_up(self):
826 if self._picklist_dropdown.IsShown(): 827 selected = self._picklist.GetFirstSelected() 828 if selected > 0: 829 self._select_picklist_row(selected-1, selected) 830 else: 831 # FIXME: input history ? 832 pass
833 #--------------------------------------------------------
834 - def __on_tab(self):
835 """Under certain circumstances take special action on <TAB>. 836 837 returns: 838 True: <TAB> was handled 839 False: <TAB> was not handled 840 841 -> can be used to decide whether to do further <TAB> handling outside this class 842 """ 843 # are we seeing the picklist ? 844 if not self._picklist_dropdown.IsShown(): 845 return False 846 847 # with only one candidate ? 848 if len(self._current_match_candidates) != 1: 849 return False 850 851 # and do we require the input to be picked from the candidates ? 852 if not self.selection_only: 853 return False 854 855 # then auto-select that item 856 self._select_picklist_row(new_row_idx = 0) 857 self._on_list_item_selected() 858 859 return True
860 #-------------------------------------------------------- 861 # timer handling 862 #--------------------------------------------------------
863 - def __init_timer(self):
864 self.__timer = _cPRWTimer() 865 self.__timer.callback = self._on_timer_fired 866 # initially stopped 867 self.__timer.Stop()
868 #--------------------------------------------------------
869 - def _on_timer_fired(self):
870 """Callback for delayed match retrieval timer. 871 872 if we end up here: 873 - delay has passed without user input 874 - the value in the input field has not changed since the timer started 875 """ 876 # update matches according to current input 877 val = self._extract_fragment_to_match_on() 878 self._update_candidates_in_picklist(val = val) 879 880 # we now have either: 881 # - all possible items (within reasonable limits) if input was '*' 882 # - all matching items 883 # - an empty match list if no matches were found 884 # also, our picklist is refilled and sorted according to weight 885 wx.CallAfter(self._show_picklist, input2match = val)
886 #---------------------------------------------------- 887 # random helpers and properties 888 #----------------------------------------------------
889 - def __mac_log(self, msg):
890 if self.__use_fake_popup: 891 _log.debug(msg)
892 #--------------------------------------------------------
893 - def __char_is_allowed(self, char=None):
894 # if undefined accept all chars 895 if self.accepted_chars is None: 896 return True 897 return (self.__accepted_chars.match(char) is not None)
898 #--------------------------------------------------------
899 - def _set_accepted_chars(self, accepted_chars=None):
900 if accepted_chars is None: 901 self.__accepted_chars = None 902 else: 903 self.__accepted_chars = regex.compile(accepted_chars)
904
905 - def _get_accepted_chars(self):
906 if self.__accepted_chars is None: 907 return None 908 return self.__accepted_chars.pattern
909 910 accepted_chars = property(_get_accepted_chars, _set_accepted_chars) 911 #--------------------------------------------------------
912 - def _set_final_regex(self, final_regex='.*'):
913 self.__final_regex = regex.compile(final_regex, flags = regex.LOCALE | regex.UNICODE)
914
915 - def _get_final_regex(self):
916 return self.__final_regex.pattern
917 918 final_regex = property(_get_final_regex, _set_final_regex) 919 #--------------------------------------------------------
920 - def _set_final_regex_error_msg(self, msg):
921 self.__final_regex_error_msg = msg % self.final_regex
922
923 - def _get_final_regex_error_msg(self):
924 return self.__final_regex_error_msg
925 926 final_regex_error_msg = property(_get_final_regex_error_msg, _set_final_regex_error_msg) 927 #-------------------------------------------------------- 928 # data munging 929 #--------------------------------------------------------
930 - def _set_data_to_first_match(self):
931 return False
932 #--------------------------------------------------------
933 - def _update_data_from_picked_item(self, item):
934 self.data = {item['field_label']: item}
935 #--------------------------------------------------------
936 - def _dictify_data(self, data=None, value=None):
937 raise NotImplementedError('[%s]: _dictify_data()' % self.__class__.__name__)
938 #---------------------------------------------------------
940 raise NotImplementedError('[%s]: cannot adjust data after text update' % self.__class__.__name__)
941 #--------------------------------------------------------
942 - def _data2match(self, data):
943 if self.matcher is None: 944 return None 945 return self.matcher.get_match_by_data(data = data)
946 #--------------------------------------------------------
947 - def _create_data(self):
948 raise NotImplementedError('[%s]: cannot create data object' % self.__class__.__name__)
949 #--------------------------------------------------------
950 - def _get_data(self):
951 return self._data
952
953 - def _set_data(self, data):
954 self._data = data 955 self.__recalculate_tooltip()
956 957 data = property(_get_data, _set_data)
958 959 #============================================================ 960 # FIXME: cols in pick list 961 # FIXME: snap_to_basename+set selection 962 # FIXME: learn() -> PWL 963 # FIXME: up-arrow: show recent (in-memory) history 964 #---------------------------------------------------------- 965 # ideas 966 #---------------------------------------------------------- 967 #- display possible completion but highlighted for deletion 968 #(- cycle through possible completions) 969 #- pre-fill selection with SELECT ... LIMIT 25 970 #- async threads for match retrieval instead of timer 971 # - on truncated results return item "..." -> selection forcefully retrieves all matches 972 973 #- generators/yield() 974 #- OnChar() - process a char event 975 976 # split input into words and match components against known phrases 977 978 # make special list window: 979 # - deletion of items 980 # - highlight matched parts 981 # - faster scrolling 982 # - wxEditableListBox ? 983 984 # - if non-learning (i.e. fast select only): autocomplete with match 985 # and move cursor to end of match 986 #----------------------------------------------------------------------------------------------- 987 # darn ! this clever hack won't work since we may have crossed a search location threshold 988 #---- 989 # #self.__prevFragment = "***********-very-unlikely--------------***************" 990 # #self.__prevMatches = [] # a list of tuples (ID, listbox name, weight) 991 # 992 # # is the current fragment just a longer version of the previous fragment ? 993 # if string.find(aFragment, self.__prevFragment) == 0: 994 # # we then need to search in the previous matches only 995 # for prevMatch in self.__prevMatches: 996 # if string.find(prevMatch[1], aFragment) == 0: 997 # matches.append(prevMatch) 998 # # remember current matches 999 # self.__prefMatches = matches 1000 # # no matches found 1001 # if len(matches) == 0: 1002 # return [(1,_('*no matching items found*'),1)] 1003 # else: 1004 # return matches 1005 #---- 1006 #TODO: 1007 # - see spincontrol for list box handling 1008 # stop list (list of negatives): "an" -> "animal" but not "and" 1009 #----- 1010 #> > remember, you should be searching on either weighted data, or in some 1011 #> > situations a start string search on indexed data 1012 #> 1013 #> Can you be a bit more specific on this ? 1014 1015 #seaching ones own previous text entered would usually be instring but 1016 #weighted (ie the phrases you use the most auto filter to the top) 1017 1018 #Searching a drug database for a drug brand name is usually more 1019 #functional if it does a start string search, not an instring search which is 1020 #much slower and usually unecesary. There are many other examples but trust 1021 #me one needs both 1022 1023 # FIXME: support selection-only-or-empty 1024 1025 1026 #============================================================
1027 -class cPhraseWheel(cPhraseWheelBase):
1028
1029 - def GetData(self, can_create=False, as_instance=False):
1030 1031 super(cPhraseWheel, self).GetData(can_create = can_create) 1032 1033 if len(self._data) > 0: 1034 if as_instance: 1035 return self._data2instance() 1036 1037 if len(self._data) == 0: 1038 return None 1039 1040 return self._data.values()[0]['data']
1041 #---------------------------------------------------------
1042 - def SetData(self, data=None):
1043 """Set the data and thereby set the value, too. if possible. 1044 1045 If you call SetData() you better be prepared 1046 doing a scan of the entire potential match space. 1047 1048 The whole thing will only work if data is found 1049 in the match space anyways. 1050 """ 1051 # try getting match candidates 1052 self._update_candidates_in_picklist(u'*') 1053 1054 # do we require a match ? 1055 if self.selection_only: 1056 # yes, but we don't have any candidates 1057 if len(self._current_match_candidates) == 0: 1058 return False 1059 1060 # among candidates look for a match with <data> 1061 for candidate in self._current_match_candidates: 1062 if candidate['data'] == data: 1063 super(cPhraseWheel, self).SetText ( 1064 value = candidate['field_label'], 1065 data = data, 1066 suppress_smarts = True 1067 ) 1068 return True 1069 1070 # no match found in candidates (but needed) ... 1071 if self.selection_only: 1072 self.display_as_valid(valid = False) 1073 return False 1074 1075 self.data = self._dictify_data(data = data) 1076 self.display_as_valid(valid = True) 1077 return True
1078 #-------------------------------------------------------- 1079 # internal API 1080 #--------------------------------------------------------
1081 - def _show_picklist(self, input2match):
1082 1083 # this helps if the current input was already selected from the 1084 # list but still is the substring of another pick list item or 1085 # else the picklist will re-open just after selection 1086 if len(self._data) > 0: 1087 self._picklist_dropdown.Hide() 1088 return 1089 1090 return super(cPhraseWheel, self)._show_picklist(input2match = input2match)
1091 #--------------------------------------------------------
1092 - def _set_data_to_first_match(self):
1093 # data already set ? 1094 if len(self._data) > 0: 1095 return True 1096 1097 # needed ? 1098 val = self.GetValue().strip() 1099 if val == u'': 1100 return True 1101 1102 # so try 1103 self._update_candidates_in_picklist(val = val) 1104 for candidate in self._current_match_candidates: 1105 if candidate['field_label'] == val: 1106 self.data = {candidate['field_label']: candidate} 1107 self.MarkDirty() 1108 # tell listeners about the user's selection 1109 for callback in self._on_selection_callbacks: 1110 callback(self._data) 1111 return True 1112 1113 # no exact match found 1114 if self.selection_only: 1115 gmDispatcher.send(signal = 'statustext', msg = self.selection_only_error_msg) 1116 is_valid = False 1117 return False 1118 1119 return True
1120 #---------------------------------------------------------
1122 self.data = {}
1123 #---------------------------------------------------------
1125 return self.GetValue().strip()
1126 #---------------------------------------------------------
1127 - def _dictify_data(self, data=None, value=None):
1128 # assume data to always be old style 1129 if value is None: 1130 value = u'%s' % data 1131 return {value: {'data': data, 'list_label': value, 'field_label': value}}
1132 #============================================================
1133 -class cMultiPhraseWheel(cPhraseWheelBase):
1134
1135 - def __init__(self, *args, **kwargs):
1136 1137 super(cMultiPhraseWheel, self).__init__(*args, **kwargs) 1138 1139 self.phrase_separators = default_phrase_separators 1140 self.left_part = u'' 1141 self.right_part = u'' 1142 self.speller = None
1143 #---------------------------------------------------------
1144 - def GetData(self, can_create=False, as_instance=False):
1145 1146 super(cMultiPhraseWheel, self).GetData(can_create = can_create) 1147 1148 if len(self._data) > 0: 1149 if as_instance: 1150 return self._data2instance() 1151 1152 return self._data.values()
1153 #---------------------------------------------------------
1155 self.speller = None 1156 return True
1157 #---------------------------------------------------------
1158 - def list2data_dict(self, data_items=None):
1159 1160 data_dict = {} 1161 1162 for item in data_items: 1163 try: 1164 list_label = item['list_label'] 1165 except KeyError: 1166 list_label = item['label'] 1167 try: 1168 field_label = item['field_label'] 1169 except KeyError: 1170 field_label = list_label 1171 data_dict[field_label] = {'data': item['data'], 'list_label': list_label, 'field_label': field_label} 1172 1173 return data_dict
1174 #--------------------------------------------------------- 1175 # internal API 1176 #---------------------------------------------------------
1177 - def _get_suggestions_from_speller(self, val):
1178 return None
1179 #---------------------------------------------------------
1181 # the textctrl display must already be set properly 1182 new_data = {} 1183 # this way of looping automatically removes stale 1184 # data for labels which are no longer displayed 1185 for displayed_label in self.displayed_strings: 1186 try: 1187 new_data[displayed_label] = self._data[displayed_label] 1188 except KeyError: 1189 # this removes stale data for which there 1190 # is no displayed_label anymore 1191 pass 1192 1193 self.data = new_data
1194 #---------------------------------------------------------
1196 1197 cursor_pos = self.GetInsertionPoint() 1198 1199 entire_input = self.GetValue() 1200 if self.__phrase_separators.search(entire_input) is None: 1201 self.left_part = u'' 1202 self.right_part = u'' 1203 return self.GetValue().strip() 1204 1205 string_left_of_cursor = entire_input[:cursor_pos] 1206 string_right_of_cursor = entire_input[cursor_pos:] 1207 1208 left_parts = [ lp.strip() for lp in self.__phrase_separators.split(string_left_of_cursor) ] 1209 if len(left_parts) == 0: 1210 self.left_part = u'' 1211 else: 1212 self.left_part = u'%s%s ' % ( 1213 (u'%s ' % self.__phrase_separators.pattern[0]).join(left_parts[:-1]), 1214 self.__phrase_separators.pattern[0] 1215 ) 1216 1217 right_parts = [ rp.strip() for rp in self.__phrase_separators.split(string_right_of_cursor) ] 1218 self.right_part = u'%s %s' % ( 1219 self.__phrase_separators.pattern[0], 1220 (u'%s ' % self.__phrase_separators.pattern[0]).join(right_parts[1:]) 1221 ) 1222 1223 val = (left_parts[-1] + right_parts[0]).strip() 1224 return val
1225 #--------------------------------------------------------
1226 - def _update_display_from_picked_item(self, item):
1227 val = (u'%s%s%s' % ( 1228 self.left_part, 1229 self._picklist_item2display_string(item = item), 1230 self.right_part 1231 )).lstrip().lstrip(';').strip() 1232 self.suppress_text_update_smarts = True 1233 super(cMultiPhraseWheel, self).SetValue(val) 1234 # find item end and move cursor to that place: 1235 item_end = val.index(item['field_label']) + len(item['field_label']) 1236 self.SetInsertionPoint(item_end) 1237 return
1238 #--------------------------------------------------------
1239 - def _update_data_from_picked_item(self, item):
1240 1241 # add item to the data 1242 self._data[item['field_label']] = item 1243 1244 # the textctrl display must already be set properly 1245 field_labels = [ p.strip() for p in self.__phrase_separators.split(self.GetValue().strip()) ] 1246 new_data = {} 1247 # this way of looping automatically removes stale 1248 # data for labels which are no longer displayed 1249 for field_label in field_labels: 1250 try: 1251 new_data[field_label] = self._data[field_label] 1252 except KeyError: 1253 # this removes stale data for which there 1254 # is no displayed_label anymore 1255 pass 1256 1257 self.data = new_data
1258 #---------------------------------------------------------
1259 - def _dictify_data(self, data=None, value=None):
1260 if type(data) == type([]): 1261 # useful because self.GetData() returns just such a list 1262 return self.list2data_dict(data_items = data) 1263 # else assume new-style already-dictified data 1264 return data
1265 #-------------------------------------------------------- 1266 # properties 1267 #--------------------------------------------------------
1268 - def _set_phrase_separators(self, phrase_separators):
1269 """Set phrase separators. 1270 1271 - must be a valid regular expression pattern 1272 1273 input is split into phrases at boundaries defined by 1274 this regex and matching is performed on the phrase 1275 the cursor is in only, 1276 1277 after selection from picklist phrase_separators[0] is 1278 added to the end of the match in the PRW 1279 """ 1280 self.__phrase_separators = regex.compile(phrase_separators, flags = regex.LOCALE | regex.UNICODE)
1281
1282 - def _get_phrase_separators(self):
1283 return self.__phrase_separators.pattern
1284 1285 phrase_separators = property(_get_phrase_separators, _set_phrase_separators) 1286 #--------------------------------------------------------
1287 - def _get_displayed_strings(self):
1288 return [ p.strip() for p in self.__phrase_separators.split(self.GetValue().strip()) if p.strip() != u'' ]
1289 1290 displayed_strings = property(_get_displayed_strings, lambda x:x)
1291 #============================================================ 1292 # main 1293 #------------------------------------------------------------ 1294 if __name__ == '__main__': 1295 1296 if len(sys.argv) < 2: 1297 sys.exit() 1298 1299 if sys.argv[1] != u'test': 1300 sys.exit() 1301 1302 from Gnumed.pycommon import gmI18N 1303 gmI18N.activate_locale() 1304 gmI18N.install_domain(domain='gnumed') 1305 1306 from Gnumed.pycommon import gmPG2, gmMatchProvider 1307 1308 prw = None # used for access from display_values_* 1309 #--------------------------------------------------------
1310 - def display_values_set_focus(*args, **kwargs):
1311 print "got focus:" 1312 print "value:", prw.GetValue() 1313 print "data :", prw.GetData() 1314 return True
1315 #--------------------------------------------------------
1316 - def display_values_lose_focus(*args, **kwargs):
1317 print "lost focus:" 1318 print "value:", prw.GetValue() 1319 print "data :", prw.GetData() 1320 return True
1321 #--------------------------------------------------------
1322 - def display_values_modified(*args, **kwargs):
1323 print "modified:" 1324 print "value:", prw.GetValue() 1325 print "data :", prw.GetData() 1326 return True
1327 #--------------------------------------------------------
1328 - def display_values_selected(*args, **kwargs):
1329 print "selected:" 1330 print "value:", prw.GetValue() 1331 print "data :", prw.GetData() 1332 return True
1333 #-------------------------------------------------------- 1334 #--------------------------------------------------------
1335 - def test_prw_fixed_list():
1336 app = wx.PyWidgetTester(size = (200, 50)) 1337 1338 items = [ {'data': 1, 'list_label': "Bloggs", 'field_label': "Bloggs", 'weight': 0}, 1339 {'data': 2, 'list_label': "Baker", 'field_label': "Baker", 'weight': 0}, 1340 {'data': 3, 'list_label': "Jones", 'field_label': "Jones", 'weight': 0}, 1341 {'data': 4, 'list_label': "Judson", 'field_label': "Judson", 'weight': 0}, 1342 {'data': 5, 'list_label': "Jacobs", 'field_label': "Jacobs", 'weight': 0}, 1343 {'data': 6, 'list_label': "Judson-Jacobs", 'field_label': "Judson-Jacobs", 'weight': 0} 1344 ] 1345 1346 mp = gmMatchProvider.cMatchProvider_FixedList(items) 1347 # do NOT treat "-" as a word separator here as there are names like "asa-sismussen" 1348 mp.word_separators = '[ \t=+&:@]+' 1349 global prw 1350 prw = cPhraseWheel(parent = app.frame, id = -1) 1351 prw.matcher = mp 1352 prw.capitalisation_mode = gmTools.CAPS_NAMES 1353 prw.add_callback_on_set_focus(callback=display_values_set_focus) 1354 prw.add_callback_on_modified(callback=display_values_modified) 1355 prw.add_callback_on_lose_focus(callback=display_values_lose_focus) 1356 prw.add_callback_on_selection(callback=display_values_selected) 1357 1358 app.frame.Show(True) 1359 app.MainLoop() 1360 1361 return True
1362 #--------------------------------------------------------
1363 - def test_prw_sql2():
1364 print "Do you want to test the database connected phrase wheel ?" 1365 yes_no = raw_input('y/n: ') 1366 if yes_no != 'y': 1367 return True 1368 1369 gmPG2.get_connection() 1370 query = u"""SELECT code, code || ': ' || _(name), _(name) FROM dem.country WHERE _(name) %(fragment_condition)s""" 1371 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query]) 1372 app = wx.PyWidgetTester(size = (400, 50)) 1373 global prw 1374 #prw = cPhraseWheel(parent = app.frame, id = -1) 1375 prw = cMultiPhraseWheel(parent = app.frame, id = -1) 1376 prw.matcher = mp 1377 1378 app.frame.Show(True) 1379 app.MainLoop() 1380 1381 return True
1382 #--------------------------------------------------------
1383 - def test_prw_patients():
1384 gmPG2.get_connection() 1385 query = u""" 1386 select 1387 pk_identity, 1388 firstnames || ' ' || lastnames || ', ' || to_char(dob, 'YYYY-MM-DD'), 1389 firstnames || ' ' || lastnames 1390 from 1391 dem.v_basic_person 1392 where 1393 firstnames || lastnames %(fragment_condition)s 1394 """ 1395 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query]) 1396 app = wx.PyWidgetTester(size = (500, 50)) 1397 global prw 1398 prw = cPhraseWheel(parent = app.frame, id = -1) 1399 prw.matcher = mp 1400 prw.selection_only = True 1401 1402 app.frame.Show(True) 1403 app.MainLoop() 1404 1405 return True
1406 #--------------------------------------------------------
1407 - def test_spell_checking_prw():
1408 app = wx.PyWidgetTester(size = (200, 50)) 1409 1410 global prw 1411 prw = cPhraseWheel(parent = app.frame, id = -1) 1412 1413 prw.add_callback_on_set_focus(callback=display_values_set_focus) 1414 prw.add_callback_on_modified(callback=display_values_modified) 1415 prw.add_callback_on_lose_focus(callback=display_values_lose_focus) 1416 prw.add_callback_on_selection(callback=display_values_selected) 1417 1418 prw.enable_default_spellchecker() 1419 1420 app.frame.Show(True) 1421 app.MainLoop() 1422 1423 return True
1424 #-------------------------------------------------------- 1425 #test_prw_fixed_list() 1426 #test_prw_sql2() 1427 #test_spell_checking_prw() 1428 test_prw_patients() 1429 1430 #================================================== 1431