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