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

Source Code for Module Gnumed.wxpython.gmPatSearchWidgets

   1  #  coding: latin-1 
   2  """GNUmed quick person search widgets. 
   3   
   4  This widget allows to search for persons based on the 
   5  critera name, date of birth and person ID. It goes to 
   6  considerable lengths to understand the user's intent from 
   7  her input. For that to work well we need per-culture 
   8  query generators. However, there's always the fallback 
   9  generator. 
  10  """ 
  11  #============================================================ 
  12  __version__ = "$Revision: 1.132 $" 
  13  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  14  __license__ = 'GPL (for details see http://www.gnu.org/)' 
  15   
  16  import sys, os.path, glob, datetime as pyDT, re as regex, logging, webbrowser 
  17   
  18   
  19  import wx 
  20   
  21   
  22  if __name__ == '__main__': 
  23          sys.path.insert(0, '../../') 
  24          from Gnumed.pycommon import gmLog2 
  25  from Gnumed.pycommon import gmDispatcher, gmPG2, gmI18N, gmCfg, gmTools, gmDateTime, gmMatchProvider, gmCfg2 
  26  from Gnumed.business import gmPerson, gmKVK, gmSurgery 
  27  from Gnumed.wxpython import gmGuiHelpers, gmDemographicsWidgets, gmAuthWidgets, gmRegetMixin, gmPhraseWheel, gmEditArea 
  28  from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg, wxgSelectPersonDTOFromListDlg, wxgMergePatientsDlg 
  29   
  30   
  31  _log = logging.getLogger('gm.person') 
  32  _log.info(__version__) 
  33   
  34  _cfg = gmCfg2.gmCfgData() 
  35   
  36  ID_PatPickList = wx.NewId() 
  37  ID_BTN_AddNew = wx.NewId() 
  38   
  39  #============================================================ 
40 -def merge_patients(parent=None):
41 dlg = cMergePatientsDlg(parent, -1) 42 result = dlg.ShowModal()
43 #============================================================
44 -class cMergePatientsDlg(wxgMergePatientsDlg.wxgMergePatientsDlg):
45
46 - def __init__(self, *args, **kwargs):
47 wxgMergePatientsDlg.wxgMergePatientsDlg.__init__(self, *args, **kwargs) 48 49 curr_pat = gmPerson.gmCurrentPatient() 50 if curr_pat.connected: 51 self._TCTRL_patient1.person = curr_pat 52 self._TCTRL_patient1._display_name() 53 self._RBTN_patient1.SetValue(True)
54 #--------------------------------------------------------
55 - def _on_merge_button_pressed(self, event):
56 57 if self._TCTRL_patient1.person is None: 58 return 59 60 if self._TCTRL_patient2.person is None: 61 return 62 63 if self._RBTN_patient1.GetValue(): 64 patient2keep = self._TCTRL_patient1.person 65 patient2merge = self._TCTRL_patient2.person 66 else: 67 patient2keep = self._TCTRL_patient2.person 68 patient2merge = self._TCTRL_patient1.person 69 70 if patient2merge['lastnames'] == u'Kirk': 71 if _cfg.get(option = 'debug'): 72 webbrowser.open ( 73 url = 'http://en.wikipedia.org/wiki/File:Picard_as_Locutus.jpg', 74 new = False, 75 autoraise = True 76 ) 77 gmGuiHelpers.gm_show_info(_('\n\nYou will be assimilated.\n\n'), _('The Borg')) 78 return 79 else: 80 gmDispatcher.send(signal = 'statustext', msg = _('Cannot merge Kirk into another patient.'), beep = True) 81 return 82 83 doit = gmGuiHelpers.gm_show_question ( 84 aMessage = _( 85 'Are you positively sure you want to merge patient\n\n' 86 ' #%s: %s (%s, %s)\n\n' 87 'into patient\n\n' 88 ' #%s: %s (%s, %s) ?\n\n' 89 'Note that this action can ONLY be reversed by a laborious\n' 90 'manual process requiring in-depth knowledge about databases\n' 91 'and the patients in question !\n' 92 ) % ( 93 patient2merge.ID, 94 patient2merge['description_gender'], 95 patient2merge['gender'], 96 patient2merge.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()), 97 patient2keep.ID, 98 patient2keep['description_gender'], 99 patient2keep['gender'], 100 patient2keep.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()) 101 ), 102 aTitle = _('Merging patients: confirmation'), 103 cancel_button = False 104 ) 105 if not doit: 106 return 107 108 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Merging patients')) 109 if conn is None: 110 return 111 112 success, msg = patient2keep.assimilate_identity(other_identity = patient2merge, link_obj = conn) 113 conn.close() 114 if not success: 115 gmDispatcher.send(signal = 'statustext', msg = msg, beep = True) 116 return 117 118 # announce success, offer to activate kept patient if not active 119 doit = gmGuiHelpers.gm_show_question ( 120 aMessage = _( 121 'The patient\n' 122 '\n' 123 ' #%s: %s (%s, %s)\n' 124 '\n' 125 'has successfully been merged into\n' 126 '\n' 127 ' #%s: %s (%s, %s)\n' 128 '\n' 129 '\n' 130 'Do you want to activate that patient\n' 131 'now for further modifications ?\n' 132 ) % ( 133 patient2merge.ID, 134 patient2merge['description_gender'], 135 patient2merge['gender'], 136 patient2merge.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()), 137 patient2keep.ID, 138 patient2keep['description_gender'], 139 patient2keep['gender'], 140 patient2keep.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()) 141 ), 142 aTitle = _('Merging patients: success'), 143 cancel_button = False 144 ) 145 if doit: 146 if not isinstance(patient2keep, gmPerson.gmCurrentPatient): 147 wx.CallAfter(set_active_patient, patient = patient2keep) 148 149 if self.IsModal(): 150 self.EndModal(wx.ID_OK) 151 else: 152 self.Close()
153 #============================================================
154 -class cSelectPersonFromListDlg(wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg):
155
156 - def __init__(self, *args, **kwargs):
157 wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg.__init__(self, *args, **kwargs) 158 159 self.__cols = [ 160 _('Title'), 161 _('Lastname'), 162 _('Firstname'), 163 _('Nickname'), 164 _('DOB'), 165 _('Gender'), 166 _('last visit'), 167 _('found via') 168 ] 169 self.__init_ui()
170 #--------------------------------------------------------
171 - def __init_ui(self):
172 for col in range(len(self.__cols)): 173 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
174 #--------------------------------------------------------
175 - def set_persons(self, persons=None):
176 self._LCTRL_persons.DeleteAllItems() 177 178 pos = len(persons) + 1 179 if pos == 1: 180 return False 181 182 for person in persons: 183 row_num = self._LCTRL_persons.InsertStringItem(pos, label = gmTools.coalesce(person['title'], '')) 184 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = person['lastnames']) 185 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = person['firstnames']) 186 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = gmTools.coalesce(person['preferred'], '')) 187 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = person.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding())) 188 self._LCTRL_persons.SetStringItem(index = row_num, col = 5, label = gmTools.coalesce(person['l10n_gender'], '?')) 189 label = u'' 190 if person.is_patient: 191 enc = person.get_last_encounter() 192 if enc is not None: 193 label = u'%s (%s)' % (enc['started'].strftime('%x').decode(gmI18N.get_encoding()), enc['l10n_type']) 194 self._LCTRL_persons.SetStringItem(index = row_num, col = 6, label = label) 195 try: self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = person['match_type']) 196 except: 197 _log.exception('cannot set match_type field') 198 self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = u'??') 199 200 for col in range(len(self.__cols)): 201 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE) 202 203 self._BTN_select.Enable(False) 204 self._LCTRL_persons.SetFocus() 205 self._LCTRL_persons.Select(0) 206 207 self._LCTRL_persons.set_data(data=persons)
208 #--------------------------------------------------------
209 - def get_selected_person(self):
210 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
211 #-------------------------------------------------------- 212 # event handlers 213 #--------------------------------------------------------
214 - def _on_list_item_selected(self, evt):
215 self._BTN_select.Enable(True) 216 return
217 #--------------------------------------------------------
218 - def _on_list_item_activated(self, evt):
219 self._BTN_select.Enable(True) 220 if self.IsModal(): 221 self.EndModal(wx.ID_OK) 222 else: 223 self.Close()
224 #============================================================
225 -class cSelectPersonDTOFromListDlg(wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg):
226
227 - def __init__(self, *args, **kwargs):
228 wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg.__init__(self, *args, **kwargs) 229 230 self.__cols = [ 231 _('Source'), 232 _('Lastname'), 233 _('Firstname'), 234 _('DOB'), 235 _('Gender') 236 ] 237 self.__init_ui()
238 #--------------------------------------------------------
239 - def __init_ui(self):
240 for col in range(len(self.__cols)): 241 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
242 #--------------------------------------------------------
243 - def set_dtos(self, dtos=None):
244 self._LCTRL_persons.DeleteAllItems() 245 246 pos = len(dtos) + 1 247 if pos == 1: 248 return False 249 250 for rec in dtos: 251 row_num = self._LCTRL_persons.InsertStringItem(pos, label = rec['source']) 252 dto = rec['dto'] 253 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = dto.lastnames) 254 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = dto.firstnames) 255 if dto.dob is None: 256 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = u'') 257 else: 258 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = dto.dob.strftime('%x').decode(gmI18N.get_encoding())) 259 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = gmTools.coalesce(dto.gender, '')) 260 261 for col in range(len(self.__cols)): 262 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE) 263 264 self._BTN_select.Enable(False) 265 self._LCTRL_persons.SetFocus() 266 self._LCTRL_persons.Select(0) 267 268 self._LCTRL_persons.set_data(data=dtos)
269 #--------------------------------------------------------
270 - def get_selected_dto(self):
271 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
272 #-------------------------------------------------------- 273 # event handlers 274 #--------------------------------------------------------
275 - def _on_list_item_selected(self, evt):
276 self._BTN_select.Enable(True) 277 return
278 #--------------------------------------------------------
279 - def _on_list_item_activated(self, evt):
280 self._BTN_select.Enable(True) 281 if self.IsModal(): 282 self.EndModal(wx.ID_OK) 283 else: 284 self.Close()
285 #============================================================
286 -def load_persons_from_xdt():
287 288 bdt_files = [] 289 290 # some can be auto-detected 291 # MCS/Isynet: $DRIVE:\Winacs\TEMP\BDTxx.tmp where xx is the workplace 292 candidates = [] 293 drives = 'cdefghijklmnopqrstuvwxyz' 294 for drive in drives: 295 candidate = drive + ':\Winacs\TEMP\BDT*.tmp' 296 candidates.extend(glob.glob(candidate)) 297 for candidate in candidates: 298 path, filename = os.path.split(candidate) 299 # FIXME: add encoding ! 300 bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]}) 301 302 # some need to be configured 303 # aggregate sources 304 src_order = [ 305 ('explicit', 'return'), 306 ('workbase', 'append'), 307 ('local', 'append'), 308 ('user', 'append'), 309 ('system', 'append') 310 ] 311 xdt_profiles = _cfg.get ( 312 group = 'workplace', 313 option = 'XDT profiles', 314 source_order = src_order 315 ) 316 if xdt_profiles is None: 317 return [] 318 319 # first come first serve 320 src_order = [ 321 ('explicit', 'return'), 322 ('workbase', 'return'), 323 ('local', 'return'), 324 ('user', 'return'), 325 ('system', 'return') 326 ] 327 for profile in xdt_profiles: 328 name = _cfg.get ( 329 group = 'XDT profile %s' % profile, 330 option = 'filename', 331 source_order = src_order 332 ) 333 if name is None: 334 _log.error('XDT profile [%s] does not define a <filename>' % profile) 335 continue 336 encoding = _cfg.get ( 337 group = 'XDT profile %s' % profile, 338 option = 'encoding', 339 source_order = src_order 340 ) 341 if encoding is None: 342 _log.warning('xDT source profile [%s] does not specify an <encoding> for BDT file [%s]' % (profile, name)) 343 source = _cfg.get ( 344 group = 'XDT profile %s' % profile, 345 option = 'source', 346 source_order = src_order 347 ) 348 dob_format = _cfg.get ( 349 group = 'XDT profile %s' % profile, 350 option = 'DOB format', 351 source_order = src_order 352 ) 353 if dob_format is None: 354 _log.warning('XDT profile [%s] does not define a date of birth format in <DOB format>' % profile) 355 bdt_files.append({'file': name, 'source': source, 'encoding': encoding, 'dob_format': dob_format}) 356 357 dtos = [] 358 for bdt_file in bdt_files: 359 try: 360 # FIXME: potentially return several persons per file 361 dto = gmPerson.get_person_from_xdt ( 362 filename = bdt_file['file'], 363 encoding = bdt_file['encoding'], 364 dob_format = bdt_file['dob_format'] 365 ) 366 367 except IOError: 368 gmGuiHelpers.gm_show_info ( 369 _( 370 'Cannot access BDT file\n\n' 371 ' [%s]\n\n' 372 'to import patient.\n\n' 373 'Please check your configuration.' 374 ) % bdt_file, 375 _('Activating xDT patient') 376 ) 377 _log.exception('cannot access xDT file [%s]' % bdt_file['file']) 378 continue 379 except: 380 gmGuiHelpers.gm_show_error ( 381 _( 382 'Cannot load patient from BDT file\n\n' 383 ' [%s]' 384 ) % bdt_file, 385 _('Activating xDT patient') 386 ) 387 _log.exception('cannot read patient from xDT file [%s]' % bdt_file['file']) 388 continue 389 390 dtos.append({'dto': dto, 'source': gmTools.coalesce(bdt_file['source'], dto.source)}) 391 392 return dtos
393 #============================================================
394 -def load_persons_from_pracsoft_au():
395 396 pracsoft_files = [] 397 398 # try detecting PATIENTS.IN files 399 candidates = [] 400 drives = 'cdefghijklmnopqrstuvwxyz' 401 for drive in drives: 402 candidate = drive + ':\MDW2\PATIENTS.IN' 403 candidates.extend(glob.glob(candidate)) 404 for candidate in candidates: 405 drive, filename = os.path.splitdrive(candidate) 406 pracsoft_files.append({'file': candidate, 'source': 'PracSoft (AU): drive %s' % drive}) 407 408 # add configured one(s) 409 src_order = [ 410 ('explicit', 'append'), 411 ('workbase', 'append'), 412 ('local', 'append'), 413 ('user', 'append'), 414 ('system', 'append') 415 ] 416 fnames = _cfg.get ( 417 group = 'AU PracSoft PATIENTS.IN', 418 option = 'filename', 419 source_order = src_order 420 ) 421 422 src_order = [ 423 ('explicit', 'return'), 424 ('user', 'return'), 425 ('system', 'return'), 426 ('local', 'return'), 427 ('workbase', 'return') 428 ] 429 source = _cfg.get ( 430 group = 'AU PracSoft PATIENTS.IN', 431 option = 'source', 432 source_order = src_order 433 ) 434 435 if source is not None: 436 for fname in fnames: 437 fname = os.path.abspath(os.path.expanduser(fname)) 438 if os.access(fname, os.R_OK): 439 pracsoft_files.append({'file': os.path.expanduser(fname), 'source': source}) 440 else: 441 _log.error('cannot read [%s] in AU PracSoft profile' % fname) 442 443 # and parse them 444 dtos = [] 445 for pracsoft_file in pracsoft_files: 446 try: 447 tmp = gmPerson.get_persons_from_pracsoft_file(filename = pracsoft_file['file']) 448 except: 449 _log.exception('cannot parse PracSoft file [%s]' % pracsoft_file['file']) 450 continue 451 for dto in tmp: 452 dtos.append({'dto': dto, 'source': pracsoft_file['source']}) 453 454 return dtos
455 #============================================================
456 -def load_persons_from_kvks():
457 458 dbcfg = gmCfg.cCfgSQL() 459 kvk_dir = os.path.abspath(os.path.expanduser(dbcfg.get2 ( 460 option = 'DE.KVK.spool_dir', 461 workplace = gmSurgery.gmCurrentPractice().active_workplace, 462 bias = 'workplace', 463 default = u'/var/spool/kvkd/' 464 ))) 465 dtos = [] 466 for dto in gmKVK.get_available_kvks_as_dtos(spool_dir = kvk_dir): 467 dtos.append({'dto': dto, 'source': 'KVK'}) 468 469 return dtos
470 #============================================================
471 -def get_person_from_external_sources(parent=None, search_immediately=False, activate_immediately=False):
472 """Load patient from external source. 473 474 - scan external sources for candidates 475 - let user select source 476 - if > 1 available: always 477 - if only 1 available: depending on search_immediately 478 - search for patients matching info from external source 479 - if more than one match: 480 - let user select patient 481 - if no match: 482 - create patient 483 - activate patient 484 """ 485 # get DTOs from interfaces 486 dtos = [] 487 dtos.extend(load_persons_from_xdt()) 488 dtos.extend(load_persons_from_pracsoft_au()) 489 dtos.extend(load_persons_from_kvks()) 490 491 # no external persons 492 if len(dtos) == 0: 493 gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.')) 494 return None 495 496 # one external patient with DOB - already active ? 497 if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None): 498 dto = dtos[0]['dto'] 499 # is it already the current patient ? 500 curr_pat = gmPerson.gmCurrentPatient() 501 if curr_pat.connected: 502 key_dto = dto.firstnames + dto.lastnames + dto.dob.strftime('%Y-%m-%d') + dto.gender 503 names = curr_pat.get_active_name() 504 key_pat = names['firstnames'] + names['lastnames'] + curr_pat.get_formatted_dob(format = '%Y-%m-%d') + curr_pat['gender'] 505 _log.debug('current patient: %s' % key_pat) 506 _log.debug('dto patient : %s' % key_dto) 507 if key_dto == key_pat: 508 gmDispatcher.send(signal='statustext', msg=_('The only external patient is already active in GNUmed.'), beep=False) 509 return None 510 511 # one external person - look for internal match immediately ? 512 if (len(dtos) == 1) and search_immediately: 513 dto = dtos[0]['dto'] 514 515 # several external persons 516 else: 517 if parent is None: 518 parent = wx.GetApp().GetTopWindow() 519 dlg = cSelectPersonDTOFromListDlg(parent=parent, id=-1) 520 dlg.set_dtos(dtos=dtos) 521 result = dlg.ShowModal() 522 if result == wx.ID_CANCEL: 523 return None 524 dto = dlg.get_selected_dto()['dto'] 525 dlg.Destroy() 526 527 # search 528 idents = dto.get_candidate_identities(can_create=True) 529 if idents is None: 530 gmGuiHelpers.gm_show_info (_( 531 'Cannot create new patient:\n\n' 532 ' [%s %s (%s), %s]' 533 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())), 534 _('Activating external patient') 535 ) 536 return None 537 538 if len(idents) == 1: 539 ident = idents[0] 540 541 if len(idents) > 1: 542 if parent is None: 543 parent = wx.GetApp().GetTopWindow() 544 dlg = cSelectPersonFromListDlg(parent=parent, id=-1) 545 dlg.set_persons(persons=idents) 546 result = dlg.ShowModal() 547 if result == wx.ID_CANCEL: 548 return None 549 ident = dlg.get_selected_person() 550 dlg.Destroy() 551 552 if activate_immediately: 553 if not set_active_patient(patient = ident): 554 gmGuiHelpers.gm_show_info ( 555 _( 556 'Cannot activate patient:\n\n' 557 '%s %s (%s)\n' 558 '%s' 559 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())), 560 _('Activating external patient') 561 ) 562 return None 563 564 dto.import_extra_data(identity = ident) 565 dto.delete_from_source() 566 567 return ident
568 #============================================================
569 -class cPersonSearchCtrl(wx.TextCtrl):
570 """Widget for smart search for persons.""" 571
572 - def __init__(self, *args, **kwargs):
573 574 try: 575 kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_ENTER 576 except KeyError: 577 kwargs['style'] = wx.TE_PROCESS_ENTER 578 579 # need to explicitly process ENTER events to avoid 580 # them being handed over to the next control 581 wx.TextCtrl.__init__(self, *args, **kwargs) 582 583 self.person = None 584 585 self._tt_search_hints = _( 586 'To search for a person type any of: \n' 587 '\n' 588 ' - fragment of last or first name\n' 589 " - date of birth (can start with '$' or '*')\n" 590 " - GNUmed ID of person (can start with '#')\n" 591 ' - exterenal ID of person\n' 592 '\n' 593 'and hit <ENTER>.\n' 594 '\n' 595 'Shortcuts:\n' 596 ' <F2>\n' 597 ' - scan external sources for persons\n' 598 ' <CURSOR-UP>\n' 599 ' - recall most recently used search term\n' 600 ' <CURSOR-DOWN>\n' 601 ' - list 10 most recently found persons\n' 602 ) 603 self.SetToolTipString(self._tt_search_hints) 604 605 # FIXME: set query generator 606 self.__person_searcher = gmPerson.cPatientSearcher_SQL() 607 608 self._prev_search_term = None 609 self.__prev_idents = [] 610 self._lclick_count = 0 611 612 self.__register_events()
613 #-------------------------------------------------------- 614 # properties 615 #--------------------------------------------------------
616 - def _set_person(self, person):
617 self.__person = person 618 wx.CallAfter(self._display_name)
619
620 - def _get_person(self):
621 return self.__person
622 623 person = property(_get_person, _set_person) 624 #-------------------------------------------------------- 625 # utility methods 626 #--------------------------------------------------------
627 - def _display_name(self):
628 name = u'' 629 630 if self.person is not None: 631 name = self.person['description'] 632 633 self.SetValue(name)
634 #--------------------------------------------------------
635 - def _remember_ident(self, ident=None):
636 637 if not isinstance(ident, gmPerson.cIdentity): 638 return False 639 640 # only unique identities 641 for known_ident in self.__prev_idents: 642 if known_ident['pk_identity'] == ident['pk_identity']: 643 return True 644 645 self.__prev_idents.append(ident) 646 647 # and only 10 of them 648 if len(self.__prev_idents) > 10: 649 self.__prev_idents.pop(0) 650 651 return True
652 #-------------------------------------------------------- 653 # event handling 654 #--------------------------------------------------------
655 - def __register_events(self):
656 wx.EVT_CHAR(self, self.__on_char) 657 wx.EVT_SET_FOCUS(self, self._on_get_focus) 658 wx.EVT_KILL_FOCUS (self, self._on_loose_focus) 659 wx.EVT_TEXT_ENTER (self, self.GetId(), self.__on_enter)
660 #--------------------------------------------------------
661 - def _on_get_focus(self, evt):
662 """upon tabbing in 663 664 - select all text in the field so that the next 665 character typed will delete it 666 """ 667 wx.CallAfter(self.SetSelection, -1, -1) 668 evt.Skip()
669 #--------------------------------------------------------
670 - def _on_loose_focus(self, evt):
671 # - redraw the currently active name upon losing focus 672 673 # if we use wx.EVT_KILL_FOCUS we will also receive this event 674 # when closing our application or loosing focus to another 675 # application which is NOT what we intend to achieve, 676 # however, this is the least ugly way of doing this due to 677 # certain vagaries of wxPython (see the Wiki) 678 679 # just for good measure 680 wx.CallAfter(self.SetSelection, 0, 0) 681 682 self._display_name() 683 self._remember_ident(self.person) 684 685 evt.Skip()
686 #--------------------------------------------------------
687 - def __on_char(self, evt):
688 self._on_char(evt)
689
690 - def _on_char(self, evt):
691 """True: patient was selected. 692 False: no patient was selected. 693 """ 694 keycode = evt.GetKeyCode() 695 696 # list of previously active patients 697 if keycode == wx.WXK_DOWN: 698 evt.Skip() 699 if len(self.__prev_idents) == 0: 700 return False 701 702 dlg = cSelectPersonFromListDlg(parent = wx.GetTopLevelParent(self), id = -1) 703 dlg.set_persons(persons = self.__prev_idents) 704 result = dlg.ShowModal() 705 if result == wx.ID_OK: 706 wx.BeginBusyCursor() 707 self.person = dlg.get_selected_person() 708 dlg.Destroy() 709 wx.EndBusyCursor() 710 return True 711 712 dlg.Destroy() 713 return False 714 715 # recall previous search fragment 716 if keycode == wx.WXK_UP: 717 evt.Skip() 718 # FIXME: cycling through previous fragments 719 if self._prev_search_term is not None: 720 self.SetValue(self._prev_search_term) 721 return False 722 723 # invoke external patient sources 724 if keycode == wx.WXK_F2: 725 evt.Skip() 726 dbcfg = gmCfg.cCfgSQL() 727 search_immediately = bool(dbcfg.get2 ( 728 option = 'patient_search.external_sources.immediately_search_if_single_source', 729 workplace = gmSurgery.gmCurrentPractice().active_workplace, 730 bias = 'user', 731 default = 0 732 )) 733 p = get_person_from_external_sources ( 734 parent = wx.GetTopLevelParent(self), 735 search_immediately = search_immediately 736 ) 737 if p is not None: 738 self.person = p 739 return True 740 return False 741 742 # FIXME: invoke add new person 743 # FIXME: add popup menu apart from system one 744 745 evt.Skip()
746 #--------------------------------------------------------
747 - def __on_enter(self, evt):
748 """This is called from the ENTER handler.""" 749 750 # ENTER but no search term ? 751 curr_search_term = self.GetValue().strip() 752 if curr_search_term == '': 753 return None 754 755 # same person anywys ? 756 if self.person is not None: 757 if curr_search_term == self.person['description']: 758 return None 759 760 # remember search fragment 761 if self.IsModified(): 762 self._prev_search_term = curr_search_term 763 764 self._on_enter(search_term = curr_search_term)
765 #--------------------------------------------------------
766 - def _on_enter(self, search_term=None):
767 """This can be overridden in child classes.""" 768 769 wx.BeginBusyCursor() 770 771 # get list of matching ids 772 idents = self.__person_searcher.get_identities(search_term) 773 774 if idents is None: 775 wx.EndBusyCursor() 776 gmGuiHelpers.gm_show_info ( 777 _('Error searching for matching persons.\n\n' 778 'Search term: "%s"' 779 ) % search_term, 780 _('selecting person') 781 ) 782 return None 783 784 _log.info("%s matching person(s) found", len(idents)) 785 786 if len(idents) == 0: 787 wx.EndBusyCursor() 788 789 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 790 wx.GetTopLevelParent(self), 791 -1, 792 caption = _('Selecting patient'), 793 question = _( 794 'Cannot find any matching patients for the search term\n\n' 795 ' "%s"\n\n' 796 'You may want to try a shorter search term.\n' 797 ) % search_term, 798 button_defs = [ 799 {'label': _('Go back'), 'tooltip': _('Go back and search again.'), 'default': True}, 800 {'label': _('Create new'), 'tooltip': _('Create new patient.')} 801 ] 802 ) 803 if dlg.ShowModal() != wx.ID_NO: 804 return 805 806 success = gmDemographicsWidgets.create_new_person(activate = True) 807 if success: 808 self.person = gmPerson.gmCurrentPatient() 809 else: 810 self.person = None 811 return None 812 813 # only one matching identity 814 if len(idents) == 1: 815 self.person = idents[0] 816 wx.EndBusyCursor() 817 return None 818 819 # more than one matching identity: let user select from pick list 820 dlg = cSelectPersonFromListDlg(parent=wx.GetTopLevelParent(self), id=-1) 821 dlg.set_persons(persons=idents) 822 wx.EndBusyCursor() 823 result = dlg.ShowModal() 824 if result == wx.ID_CANCEL: 825 dlg.Destroy() 826 return None 827 828 wx.BeginBusyCursor() 829 self.person = dlg.get_selected_person() 830 dlg.Destroy() 831 wx.EndBusyCursor() 832 833 return None
834 #============================================================
835 -def set_active_patient(patient=None, forced_reload=False):
836 837 # warn if DOB is missing 838 try: 839 patient['dob'] 840 check_dob = True 841 except TypeError: 842 check_dob = False 843 844 if check_dob: 845 if patient['dob'] is None: 846 gmGuiHelpers.gm_show_warning ( 847 aTitle = _('Checking date of birth'), 848 aMessage = _( 849 '\n' 850 ' %s\n' 851 '\n' 852 'The date of birth for this patient is not known !\n' 853 '\n' 854 'You can proceed to work on the patient but\n' 855 'GNUmed will be unable to assist you with\n' 856 'age-related decisions.\n' 857 ) % patient['description_gender'] 858 ) 859 860 success = gmPerson.set_active_patient(patient = patient, forced_reload = forced_reload) 861 862 if success: 863 if patient['dob'] is not None: 864 dbcfg = gmCfg.cCfgSQL() 865 dob_distance = dbcfg.get2 ( 866 option = u'patient_search.dob_warn_interval', 867 workplace = gmSurgery.gmCurrentPractice().active_workplace, 868 bias = u'user', 869 default = u'1 week' 870 ) 871 872 if patient.dob_in_range(dob_distance, dob_distance): 873 now = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone) 874 enc = gmI18N.get_encoding() 875 gmDispatcher.send(signal = 'statustext', msg = _( 876 '%(pat)s turns %(age)s on %(month)s %(day)s ! (today is %(month_now)s %(day_now)s)') % { 877 'pat': patient.get_description_gender(), 878 'age': patient.get_medical_age().strip('y'), 879 'month': patient.get_formatted_dob(format = '%B', encoding = enc), 880 'day': patient.get_formatted_dob(format = '%d', encoding = enc), 881 'month_now': now.strftime('%B').decode(enc), 882 'day_now': now.strftime('%d') 883 } 884 ) 885 886 return success
887 #------------------------------------------------------------
888 -class cActivePatientSelector(cPersonSearchCtrl):
889
890 - def __init__ (self, *args, **kwargs):
891 892 cPersonSearchCtrl.__init__(self, *args, **kwargs) 893 894 # selector_tooltip = _( 895 # 'Patient search field. \n' 896 # '\n' 897 # 'To search, type any of:\n' 898 # ' - fragment of last or first name\n' 899 # " - date of birth (can start with '$' or '*')\n" 900 # " - patient ID (can start with '#')\n" 901 # 'and hit <ENTER>.\n' 902 # '\n' 903 # '<CURSOR-UP>\n' 904 # ' - recall most recently used search term\n' 905 # '<CURSOR-DOWN>\n' 906 # ' - list 10 most recently activated patients\n' 907 # '<F2>\n' 908 # ' - scan external sources for patients to import and activate\n' 909 # ) 910 # self.SetToolTip(wx.ToolTip(selector_tooltip)) 911 912 # get configuration 913 cfg = gmCfg.cCfgSQL() 914 915 self.__always_dismiss_on_search = bool ( 916 cfg.get2 ( 917 option = 'patient_search.always_dismiss_previous_patient', 918 workplace = gmSurgery.gmCurrentPractice().active_workplace, 919 bias = 'user', 920 default = 0 921 ) 922 ) 923 924 self.__always_reload_after_search = bool ( 925 cfg.get2 ( 926 option = 'patient_search.always_reload_new_patient', 927 workplace = gmSurgery.gmCurrentPractice().active_workplace, 928 bias = 'user', 929 default = 0 930 ) 931 ) 932 933 self.__register_events()
934 #-------------------------------------------------------- 935 # utility methods 936 #--------------------------------------------------------
937 - def _display_name(self):
938 name = _('<type here to search patient>') 939 940 curr_pat = gmPerson.gmCurrentPatient() 941 if curr_pat.connected: 942 name = curr_pat['description'] 943 if curr_pat.locked: 944 name = _('%(name)s (locked)') % {'name': name} 945 946 self.SetValue(name) 947 948 if self.person is None: 949 self.SetToolTipString(self._tt_search_hints) 950 return 951 952 tt = u'%s%s-----------------------------------\n%s' % ( 953 gmTools.coalesce(self.person['emergency_contact'], u'', _('In case of emergency contact:') + u'\n %s\n'), 954 gmTools.coalesce(self.person['comment'], u'', u'\n%s\n'), 955 self._tt_search_hints 956 ) 957 self.SetToolTipString(tt)
958 #--------------------------------------------------------
959 - def _set_person_as_active_patient(self, pat):
960 if not set_active_patient(patient=pat, forced_reload = self.__always_reload_after_search): 961 _log.error('cannot change active patient') 962 return None 963 964 self._remember_ident(pat) 965 966 return True
967 #-------------------------------------------------------- 968 # event handling 969 #--------------------------------------------------------
970 - def __register_events(self):
971 # client internal signals 972 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 973 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_name_identity_change) 974 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_name_identity_change) 975 976 gmDispatcher.connect(signal = 'patient_locked', receiver = self._on_post_patient_selection) 977 gmDispatcher.connect(signal = 'patient_unlocked', receiver = self._on_post_patient_selection)
978 #----------------------------------------------
979 - def _on_name_identity_change(self, **kwargs):
980 wx.CallAfter(self._display_name)
981 #----------------------------------------------
982 - def _on_post_patient_selection(self, **kwargs):
983 if gmPerson.gmCurrentPatient().connected: 984 self.person = gmPerson.gmCurrentPatient().patient 985 else: 986 self.person = None
987 #----------------------------------------------
988 - def _on_enter(self, search_term = None):
989 990 if self.__always_dismiss_on_search: 991 _log.warning("dismissing patient before patient search") 992 self._set_person_as_active_patient(-1) 993 994 super(self.__class__, self)._on_enter(search_term=search_term) 995 996 if self.person is None: 997 return 998 999 self._set_person_as_active_patient(self.person)
1000 #----------------------------------------------
1001 - def _on_char(self, evt):
1002 1003 success = super(self.__class__, self)._on_char(evt) 1004 if success: 1005 self._set_person_as_active_patient(self.person)
1006 #============================================================ 1007 # waiting list widgets 1008 #============================================================
1009 -class cWaitingZonePhraseWheel(gmPhraseWheel.cPhraseWheel):
1010
1011 - def __init__(self, *args, **kwargs):
1012 1013 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1014 1015 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = []) 1016 mp.setThresholds(1, 2, 2) 1017 self.matcher = mp 1018 self.selection_only = False
1019 1020 #--------------------------------------------------------
1021 - def update_matcher(self, items):
1022 self.matcher.set_items([ {'data': i, 'label': i, 'weight': 1} for i in items ])
1023 1024 #============================================================ 1025 from Gnumed.wxGladeWidgets import wxgWaitingListEntryEditAreaPnl 1026
1027 -class cWaitingListEntryEditAreaPnl(wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1028
1029 - def __init__ (self, *args, **kwargs):
1030 1031 try: 1032 self.patient = kwargs['patient'] 1033 del kwargs['patient'] 1034 except KeyError: 1035 self.patient = None 1036 1037 try: 1038 data = kwargs['entry'] 1039 del kwargs['entry'] 1040 except KeyError: 1041 data = None 1042 1043 wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl.__init__(self, *args, **kwargs) 1044 gmEditArea.cGenericEditAreaMixin.__init__(self) 1045 1046 if data is None: 1047 self.mode = 'new' 1048 else: 1049 self.data = data 1050 self.mode = 'edit' 1051 1052 praxis = gmSurgery.gmCurrentPractice() 1053 pats = praxis.waiting_list_patients 1054 zones = {} 1055 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ]) 1056 self._PRW_zone.update_matcher(items = zones.keys())
1057 #-------------------------------------------------------- 1058 # edit area mixin API 1059 #--------------------------------------------------------
1060 - def _refresh_as_new(self):
1061 if self.patient is None: 1062 self._PRW_patient.person = None 1063 self._PRW_patient.Enable(True) 1064 self._PRW_patient.SetFocus() 1065 else: 1066 self._PRW_patient.person = self.patient 1067 self._PRW_patient.Enable(False) 1068 self._PRW_comment.SetFocus() 1069 self._PRW_patient._display_name() 1070 1071 self._PRW_comment.SetValue(u'') 1072 self._PRW_zone.SetValue(u'') 1073 self._SPCTRL_urgency.SetValue(0)
1074 #--------------------------------------------------------
1075 - def _refresh_from_existing(self):
1076 self._PRW_patient.person = gmPerson.cIdentity(aPK_obj = self.data['pk_identity']) 1077 self._PRW_patient.Enable(False) 1078 self._PRW_patient._display_name() 1079 1080 self._PRW_comment.SetValue(gmTools.coalesce(self.data['comment'], u'')) 1081 self._PRW_zone.SetValue(gmTools.coalesce(self.data['waiting_zone'], u'')) 1082 self._SPCTRL_urgency.SetValue(self.data['urgency']) 1083 1084 self._PRW_comment.SetFocus()
1085 #--------------------------------------------------------
1086 - def _valid_for_save(self):
1087 validity = True 1088 1089 self.display_tctrl_as_valid(tctrl = self._PRW_patient, valid = (self._PRW_patient.person is not None)) 1090 validity = (self._PRW_patient.person is not None) 1091 1092 if validity is False: 1093 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add to waiting list. Missing essential input.')) 1094 1095 return validity
1096 #----------------------------------------------------------------
1097 - def _save_as_new(self):
1098 # FIXME: filter out dupes 1099 self._PRW_patient.person.put_on_waiting_list ( 1100 urgency = self._SPCTRL_urgency.GetValue(), 1101 comment = gmTools.none_if(self._PRW_comment.GetValue().strip(), u''), 1102 zone = gmTools.none_if(self._PRW_zone.GetValue().strip(), u'') 1103 ) 1104 # dummy: 1105 self.data = {'pk_identity': None, 'comment': None, 'waiting_zone': None, 'urgency': 0} 1106 return True
1107 #----------------------------------------------------------------
1108 - def _save_as_update(self):
1109 gmSurgery.gmCurrentPractice().update_in_waiting_list ( 1110 pk = self.data['pk_waiting_list'], 1111 urgency = self._SPCTRL_urgency.GetValue(), 1112 comment = self._PRW_comment.GetValue().strip(), 1113 zone = self._PRW_zone.GetValue().strip() 1114 ) 1115 return True
1116 #============================================================ 1117 from Gnumed.wxGladeWidgets import wxgWaitingListPnl 1118
1119 -class cWaitingListPnl(wxgWaitingListPnl.wxgWaitingListPnl, gmRegetMixin.cRegetOnPaintMixin):
1120
1121 - def __init__ (self, *args, **kwargs):
1122 1123 wxgWaitingListPnl.wxgWaitingListPnl.__init__(self, *args, **kwargs) 1124 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1125 1126 self.__current_zone = None 1127 1128 self.__init_ui() 1129 self.__register_events()
1130 #-------------------------------------------------------- 1131 # interal helpers 1132 #--------------------------------------------------------
1133 - def __init_ui(self):
1134 self._LCTRL_patients.set_columns ([ 1135 _('Zone'), 1136 _('Urgency'), 1137 #' ! ', 1138 _('Waiting time'), 1139 _('Patient'), 1140 _('Born'), 1141 _('Comment') 1142 ]) 1143 self._LCTRL_patients.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 1144 self._PRW_zone.add_callback_on_selection(callback = self._on_zone_selected) 1145 self._PRW_zone.add_callback_on_lose_focus(callback = self._on_zone_selected)
1146 #--------------------------------------------------------
1147 - def __register_events(self):
1148 gmDispatcher.connect(signal = u'waiting_list_generic_mod_db', receiver = self._on_waiting_list_modified)
1149 #--------------------------------------------------------
1150 - def __refresh_waiting_list(self):
1151 1152 praxis = gmSurgery.gmCurrentPractice() 1153 pats = praxis.waiting_list_patients 1154 1155 # set matcher to all zones currently in use 1156 zones = {} 1157 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ]) 1158 self._PRW_zone.update_matcher(items = zones.keys()) 1159 del zones 1160 1161 # filter patient list by zone and set waiting list 1162 self.__current_zone = self._PRW_zone.GetValue().strip() 1163 if self.__current_zone == u'': 1164 pats = [ p for p in pats ] 1165 else: 1166 pats = [ p for p in pats if p['waiting_zone'] == self.__current_zone ] 1167 1168 self._LCTRL_patients.set_string_items ( 1169 [ [ 1170 gmTools.coalesce(p['waiting_zone'], u''), 1171 p['urgency'], 1172 p['waiting_time_formatted'].replace(u'00 ', u'', 1).replace('00:', u'').lstrip('0'), 1173 u'%s, %s (%s)' % (p['lastnames'], p['firstnames'], p['l10n_gender']), 1174 p['dob'], 1175 gmTools.coalesce(p['comment'], u'') 1176 ] for p in pats 1177 ] 1178 ) 1179 self._LCTRL_patients.set_column_widths() 1180 self._LCTRL_patients.set_data(pats) 1181 self._LCTRL_patients.Refresh() 1182 self._LCTRL_patients.SetToolTipString ( _( 1183 '%s patients are waiting.\n' 1184 '\n' 1185 'Doubleclick to activate (entry will stay in list).' 1186 ) % len(pats)) 1187 1188 self._LBL_no_of_patients.SetLabel(_('(%s patients)') % len(pats)) 1189 1190 if len(pats) == 0: 1191 self._BTN_activate.Enable(False) 1192 self._BTN_activateplus.Enable(False) 1193 self._BTN_remove.Enable(False) 1194 self._BTN_edit.Enable(False) 1195 self._BTN_up.Enable(False) 1196 self._BTN_down.Enable(False) 1197 else: 1198 self._BTN_activate.Enable(True) 1199 self._BTN_activateplus.Enable(True) 1200 self._BTN_remove.Enable(True) 1201 self._BTN_edit.Enable(True) 1202 if len(pats) > 1: 1203 self._BTN_up.Enable(True) 1204 self._BTN_down.Enable(True)
1205 #-------------------------------------------------------- 1206 # event handlers 1207 #--------------------------------------------------------
1208 - def _on_zone_selected(self, zone=None):
1209 if self.__current_zone == self._PRW_zone.GetValue().strip(): 1210 return True 1211 wx.CallAfter(self.__refresh_waiting_list) 1212 return True
1213 #--------------------------------------------------------
1214 - def _on_waiting_list_modified(self, *args, **kwargs):
1215 wx.CallAfter(self._schedule_data_reget)
1216 #--------------------------------------------------------
1217 - def _on_list_item_activated(self, evt):
1218 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1219 if item is None: 1220 return 1221 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1222 wx.CallAfter(set_active_patient, patient = pat)
1223 #--------------------------------------------------------
1224 - def _on_activate_button_pressed(self, evt):
1225 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1226 if item is None: 1227 return 1228 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1229 wx.CallAfter(set_active_patient, patient = pat)
1230 #--------------------------------------------------------
1231 - def _on_activateplus_button_pressed(self, evt):
1232 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1233 if item is None: 1234 return 1235 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1236 gmSurgery.gmCurrentPractice().remove_from_waiting_list(pk = item['pk_waiting_list']) 1237 wx.CallAfter(set_active_patient, patient = pat)
1238 #--------------------------------------------------------
1239 - def _on_add_patient_button_pressed(self, evt):
1240 1241 curr_pat = gmPerson.gmCurrentPatient() 1242 if not curr_pat.connected: 1243 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add waiting list entry: No patient selected.'), beep = True) 1244 return 1245 1246 ea = cWaitingListEntryEditAreaPnl(self, -1, patient = curr_pat) 1247 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1248 dlg.ShowModal() 1249 dlg.Destroy()
1250 #--------------------------------------------------------
1251 - def _on_edit_button_pressed(self, event):
1252 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1253 if item is None: 1254 return 1255 ea = cWaitingListEntryEditAreaPnl(self, -1, entry = item) 1256 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1257 dlg.ShowModal() 1258 dlg.Destroy()
1259 #--------------------------------------------------------
1260 - def _on_remove_button_pressed(self, evt):
1261 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1262 if item is None: 1263 return 1264 gmSurgery.gmCurrentPractice().remove_from_waiting_list(pk = item['pk_waiting_list'])
1265 #--------------------------------------------------------
1266 - def _on_up_button_pressed(self, evt):
1267 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1268 if item is None: 1269 return 1270 gmSurgery.gmCurrentPractice().raise_in_waiting_list(current_position = item['list_position'])
1271 #--------------------------------------------------------
1272 - def _on_down_button_pressed(self, evt):
1273 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1274 if item is None: 1275 return 1276 gmSurgery.gmCurrentPractice().lower_in_waiting_list(current_position = item['list_position'])
1277 #-------------------------------------------------------- 1278 # edit 1279 #-------------------------------------------------------- 1280 # reget-on-paint API 1281 #--------------------------------------------------------
1282 - def _populate_with_data(self):
1283 self.__refresh_waiting_list() 1284 return True
1285 #============================================================ 1286 # main 1287 #------------------------------------------------------------ 1288 if __name__ == "__main__": 1289 1290 if len(sys.argv) > 1: 1291 if sys.argv[1] == 'test': 1292 gmI18N.activate_locale() 1293 gmI18N.install_domain() 1294 1295 app = wx.PyWidgetTester(size = (200, 40)) 1296 # app.SetWidget(cSelectPersonFromListDlg, -1) 1297 # app.SetWidget(cPersonSearchCtrl, -1) 1298 # app.SetWidget(cActivePatientSelector, -1) 1299 app.SetWidget(cWaitingListPnl, -1) 1300 app.MainLoop() 1301 1302 #============================================================ 1303 # docs 1304 #------------------------------------------------------------ 1305 # functionality 1306 # ------------- 1307 # - hitting ENTER on non-empty field (and more than threshold chars) 1308 # - start search 1309 # - display results in a list, prefixed with numbers 1310 # - last name 1311 # - first name 1312 # - gender 1313 # - age 1314 # - city + street (no ZIP, no number) 1315 # - last visit (highlighted if within a certain interval) 1316 # - arbitrary marker (e.g. office attendance this quartal, missing KVK, appointments, due dates) 1317 # - if none found -> go to entry of new patient 1318 # - scrolling in this list 1319 # - ENTER selects patient 1320 # - ESC cancels selection 1321 # - number selects patient 1322 # 1323 # - hitting cursor-up/-down 1324 # - cycle through history of last 10 search fragments 1325 # 1326 # - hitting alt-L = List, alt-P = previous 1327 # - show list of previous ten patients prefixed with numbers 1328 # - scrolling in list 1329 # - ENTER selects patient 1330 # - ESC cancels selection 1331 # - number selects patient 1332 # 1333 # - hitting ALT-N 1334 # - immediately goes to entry of new patient 1335 # 1336 # - hitting cursor-right in a patient selection list 1337 # - pops up more detail about the patient 1338 # - ESC/cursor-left goes back to list 1339 # 1340 # - hitting TAB 1341 # - makes sure the currently active patient is displayed 1342 1343 #------------------------------------------------------------ 1344 # samples 1345 # ------- 1346 # working: 1347 # Ian Haywood 1348 # Haywood Ian 1349 # Haywood 1350 # Amador Jimenez (yes, two last names but no hyphen: Spain, for example) 1351 # Ian Haywood 19/12/1977 1352 # 19/12/1977 1353 # 19-12-1977 1354 # 19.12.1977 1355 # 19771219 1356 # $dob 1357 # *dob 1358 # #ID 1359 # ID 1360 # HIlbert, karsten 1361 # karsten, hilbert 1362 # kars, hilb 1363 # 1364 # non-working: 1365 # Haywood, Ian <40 1366 # ?, Ian 1977 1367 # Ian Haywood, 19/12/77 1368 # PUPIC 1369 # "hilb; karsten, 23.10.74" 1370 1371 #------------------------------------------------------------ 1372 # notes 1373 # ----- 1374 # >> 3. There are countries in which people have more than one 1375 # >> (significant) lastname (spanish-speaking countries are one case :), some 1376 # >> asian countries might be another one). 1377 # -> we need per-country query generators ... 1378 1379 # search case sensitive by default, switch to insensitive if not found ? 1380 1381 # accent insensitive search: 1382 # select * from * where to_ascii(column, 'encoding') like '%test%'; 1383 # may not work with Unicode 1384 1385 # phrase wheel is most likely too slow 1386 1387 # extend search fragment history 1388 1389 # ask user whether to send off level 3 queries - or thread them 1390 1391 # we don't expect patient IDs in complicated patterns, hence any digits signify a date 1392 1393 # FIXME: make list window fit list size ... 1394 1395 # clear search field upon get-focus ? 1396 1397 # F1 -> context help with hotkey listing 1398 1399 # th -> th|t 1400 # v/f/ph -> f|v|ph 1401 # maybe don't do umlaut translation in the first 2-3 letters 1402 # such that not to defeat index use for the first level query ? 1403 1404 # user defined function key to start search 1405