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