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  # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmPatSearchWidgets.py,v $ 
  13  # $Id: gmPatSearchWidgets.py,v 1.130 2009/12/21 15:12:29 ncq Exp $ 
  14  __version__ = "$Revision: 1.130 $" 
  15  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  16  __license__ = 'GPL (for details see http://www.gnu.org/)' 
  17   
  18  import sys, os.path, glob, datetime as pyDT, re as regex, logging, webbrowser 
  19   
  20   
  21  import wx 
  22   
  23   
  24  if __name__ == '__main__': 
  25          sys.path.insert(0, '../../') 
  26          from Gnumed.pycommon import gmLog2 
  27  from Gnumed.pycommon import gmDispatcher, gmPG2, gmI18N, gmCfg, gmTools, gmDateTime, gmMatchProvider, gmCfg2 
  28  from Gnumed.business import gmPerson, gmKVK, gmSurgery 
  29  from Gnumed.wxpython import gmGuiHelpers, gmDemographicsWidgets, gmAuthWidgets, gmRegetMixin, gmPhraseWheel, gmEditArea 
  30  from Gnumed.wxGladeWidgets import wxgSelectPersonFromListDlg, wxgSelectPersonDTOFromListDlg, wxgMergePatientsDlg 
  31   
  32   
  33  _log = logging.getLogger('gm.person') 
  34  _log.info(__version__) 
  35   
  36  _cfg = gmCfg2.gmCfgData() 
  37   
  38  ID_PatPickList = wx.NewId() 
  39  ID_BTN_AddNew = wx.NewId() 
  40   
  41  #============================================================ 
42 -def merge_patients(parent=None):
43 dlg = cMergePatientsDlg(parent, -1) 44 result = dlg.ShowModal()
45 #============================================================
46 -class cMergePatientsDlg(wxgMergePatientsDlg.wxgMergePatientsDlg):
47
48 - def __init__(self, *args, **kwargs):
49 wxgMergePatientsDlg.wxgMergePatientsDlg.__init__(self, *args, **kwargs) 50 51 curr_pat = gmPerson.gmCurrentPatient() 52 if curr_pat.connected: 53 self._TCTRL_patient1.person = curr_pat 54 self._TCTRL_patient1._display_name() 55 self._RBTN_patient1.SetValue(True)
56 #--------------------------------------------------------
57 - def _on_merge_button_pressed(self, event):
58 59 if self._TCTRL_patient1.person is None: 60 return 61 62 if self._TCTRL_patient2.person is None: 63 return 64 65 if self._RBTN_patient1.GetValue(): 66 patient2keep = self._TCTRL_patient1.person 67 patient2merge = self._TCTRL_patient2.person 68 else: 69 patient2keep = self._TCTRL_patient2.person 70 patient2merge = self._TCTRL_patient1.person 71 72 if patient2merge['lastnames'] == u'Kirk': 73 if _cfg.get(option = 'debug'): 74 webbrowser.open ( 75 url = 'http://en.wikipedia.org/wiki/File:Picard_as_Locutus.jpg', 76 new = False, 77 autoraise = True 78 ) 79 gmGuiHelpers.gm_show_info(_('\n\nYou will be assimilated.\n\n'), _('The Borg')) 80 return 81 else: 82 gmDispatcher.send(signal = 'statustext', msg = _('Cannot merge Kirk into another patient.'), beep = True) 83 return 84 85 doit = gmGuiHelpers.gm_show_question ( 86 aMessage = _( 87 'Are you positively sure you want to merge patient\n\n' 88 ' #%s: %s (%s, %s)\n\n' 89 'into patient\n\n' 90 ' #%s: %s (%s, %s) ?\n\n' 91 'Note that this action can ONLY be reversed by a laborious\n' 92 'manual process requiring in-depth knowledge about databases\n' 93 'and the patients in question !\n' 94 ) % ( 95 patient2merge.ID, 96 patient2merge['description_gender'], 97 patient2merge['gender'], 98 patient2merge.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()), 99 patient2keep.ID, 100 patient2keep['description_gender'], 101 patient2keep['gender'], 102 patient2keep.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()) 103 ), 104 aTitle = _('Merging patients: confirmation'), 105 cancel_button = False 106 ) 107 if not doit: 108 return 109 110 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('Merging patients')) 111 if conn is None: 112 return 113 114 success, msg = patient2keep.assimilate_identity(other_identity = patient2merge, link_obj = conn) 115 conn.close() 116 if not success: 117 gmDispatcher.send(signal = 'statustext', msg = msg, beep = True) 118 return 119 120 # announce success, offer to activate kept patient if not active 121 doit = gmGuiHelpers.gm_show_question ( 122 aMessage = _( 123 'The patient\n' 124 '\n' 125 ' #%s: %s (%s, %s)\n' 126 '\n' 127 'has successfully been merged into\n' 128 '\n' 129 ' #%s: %s (%s, %s)\n' 130 '\n' 131 '\n' 132 'Do you want to activate that patient\n' 133 'now for further modifications ?\n' 134 ) % ( 135 patient2merge.ID, 136 patient2merge['description_gender'], 137 patient2merge['gender'], 138 patient2merge.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()), 139 patient2keep.ID, 140 patient2keep['description_gender'], 141 patient2keep['gender'], 142 patient2keep.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()) 143 ), 144 aTitle = _('Merging patients: success'), 145 cancel_button = False 146 ) 147 if doit: 148 if not isinstance(patient2keep, gmPerson.gmCurrentPatient): 149 wx.CallAfter(set_active_patient, patient = patient2keep) 150 151 if self.IsModal(): 152 self.EndModal(wx.ID_OK) 153 else: 154 self.Close()
155 #============================================================
156 -class cSelectPersonFromListDlg(wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg):
157
158 - def __init__(self, *args, **kwargs):
159 wxgSelectPersonFromListDlg.wxgSelectPersonFromListDlg.__init__(self, *args, **kwargs) 160 161 self.__cols = [ 162 _('Title'), 163 _('Lastname'), 164 _('Firstname'), 165 _('Nickname'), 166 _('DOB'), 167 _('Gender'), 168 _('last visit'), 169 _('found via') 170 ] 171 self.__init_ui()
172 #--------------------------------------------------------
173 - def __init_ui(self):
174 for col in range(len(self.__cols)): 175 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
176 #--------------------------------------------------------
177 - def set_persons(self, persons=None):
178 self._LCTRL_persons.DeleteAllItems() 179 180 pos = len(persons) + 1 181 if pos == 1: 182 return False 183 184 for person in persons: 185 row_num = self._LCTRL_persons.InsertStringItem(pos, label = gmTools.coalesce(person['title'], '')) 186 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = person['lastnames']) 187 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = person['firstnames']) 188 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = gmTools.coalesce(person['preferred'], '')) 189 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = person.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding())) 190 self._LCTRL_persons.SetStringItem(index = row_num, col = 5, label = gmTools.coalesce(person['l10n_gender'], '?')) 191 label = u'' 192 if person.is_patient: 193 enc = person.get_last_encounter() 194 if enc is not None: 195 label = u'%s (%s)' % (enc['started'].strftime('%x').decode(gmI18N.get_encoding()), enc['l10n_type']) 196 self._LCTRL_persons.SetStringItem(index = row_num, col = 6, label = label) 197 try: self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = person['match_type']) 198 except: 199 _log.exception('cannot set match_type field') 200 self._LCTRL_persons.SetStringItem(index = row_num, col = 7, label = u'??') 201 202 for col in range(len(self.__cols)): 203 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE) 204 205 self._BTN_select.Enable(False) 206 self._LCTRL_persons.SetFocus() 207 self._LCTRL_persons.Select(0) 208 209 self._LCTRL_persons.set_data(data=persons)
210 #--------------------------------------------------------
211 - def get_selected_person(self):
212 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
213 #-------------------------------------------------------- 214 # event handlers 215 #--------------------------------------------------------
216 - def _on_list_item_selected(self, evt):
217 self._BTN_select.Enable(True) 218 return
219 #--------------------------------------------------------
220 - def _on_list_item_activated(self, evt):
221 self._BTN_select.Enable(True) 222 if self.IsModal(): 223 self.EndModal(wx.ID_OK) 224 else: 225 self.Close()
226 #============================================================
227 -class cSelectPersonDTOFromListDlg(wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg):
228
229 - def __init__(self, *args, **kwargs):
230 wxgSelectPersonDTOFromListDlg.wxgSelectPersonDTOFromListDlg.__init__(self, *args, **kwargs) 231 232 self.__cols = [ 233 _('Source'), 234 _('Lastname'), 235 _('Firstname'), 236 _('DOB'), 237 _('Gender') 238 ] 239 self.__init_ui()
240 #--------------------------------------------------------
241 - def __init_ui(self):
242 for col in range(len(self.__cols)): 243 self._LCTRL_persons.InsertColumn(col, self.__cols[col])
244 #--------------------------------------------------------
245 - def set_dtos(self, dtos=None):
246 self._LCTRL_persons.DeleteAllItems() 247 248 pos = len(dtos) + 1 249 if pos == 1: 250 return False 251 252 for rec in dtos: 253 row_num = self._LCTRL_persons.InsertStringItem(pos, label = rec['source']) 254 dto = rec['dto'] 255 self._LCTRL_persons.SetStringItem(index = row_num, col = 1, label = dto.lastnames) 256 self._LCTRL_persons.SetStringItem(index = row_num, col = 2, label = dto.firstnames) 257 if dto.dob is None: 258 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = u'') 259 else: 260 self._LCTRL_persons.SetStringItem(index = row_num, col = 3, label = dto.dob.strftime('%x').decode(gmI18N.get_encoding())) 261 self._LCTRL_persons.SetStringItem(index = row_num, col = 4, label = gmTools.coalesce(dto.gender, '')) 262 263 for col in range(len(self.__cols)): 264 self._LCTRL_persons.SetColumnWidth(col=col, width=wx.LIST_AUTOSIZE) 265 266 self._BTN_select.Enable(False) 267 self._LCTRL_persons.SetFocus() 268 self._LCTRL_persons.Select(0) 269 270 self._LCTRL_persons.set_data(data=dtos)
271 #--------------------------------------------------------
272 - def get_selected_dto(self):
273 return self._LCTRL_persons.get_item_data(self._LCTRL_persons.GetFirstSelected())
274 #-------------------------------------------------------- 275 # event handlers 276 #--------------------------------------------------------
277 - def _on_list_item_selected(self, evt):
278 self._BTN_select.Enable(True) 279 return
280 #--------------------------------------------------------
281 - def _on_list_item_activated(self, evt):
282 self._BTN_select.Enable(True) 283 if self.IsModal(): 284 self.EndModal(wx.ID_OK) 285 else: 286 self.Close()
287 #============================================================
288 -def load_persons_from_xdt():
289 290 bdt_files = [] 291 292 # some can be auto-detected 293 # MCS/Isynet: $DRIVE:\Winacs\TEMP\BDTxx.tmp where xx is the workplace 294 candidates = [] 295 drives = 'cdefghijklmnopqrstuvwxyz' 296 for drive in drives: 297 candidate = drive + ':\Winacs\TEMP\BDT*.tmp' 298 candidates.extend(glob.glob(candidate)) 299 for candidate in candidates: 300 path, filename = os.path.split(candidate) 301 # FIXME: add encoding ! 302 bdt_files.append({'file': candidate, 'source': 'MCS/Isynet %s' % filename[-6:-4]}) 303 304 # some need to be configured 305 # aggregate sources 306 src_order = [ 307 ('explicit', 'return'), 308 ('workbase', 'append'), 309 ('local', 'append'), 310 ('user', 'append'), 311 ('system', 'append') 312 ] 313 xdt_profiles = _cfg.get ( 314 group = 'workplace', 315 option = 'XDT profiles', 316 source_order = src_order 317 ) 318 if xdt_profiles is None: 319 return [] 320 321 # first come first serve 322 src_order = [ 323 ('explicit', 'return'), 324 ('workbase', 'return'), 325 ('local', 'return'), 326 ('user', 'return'), 327 ('system', 'return') 328 ] 329 for profile in xdt_profiles: 330 name = _cfg.get ( 331 group = 'XDT profile %s' % profile, 332 option = 'filename', 333 source_order = src_order 334 ) 335 if name is None: 336 _log.error('XDT profile [%s] does not define a <filename>' % profile) 337 continue 338 encoding = _cfg.get ( 339 group = 'XDT profile %s' % profile, 340 option = 'encoding', 341 source_order = src_order 342 ) 343 if encoding is None: 344 _log.warning('xDT source profile [%s] does not specify an <encoding> for BDT file [%s]' % (profile, name)) 345 source = _cfg.get ( 346 group = 'XDT profile %s' % profile, 347 option = 'source', 348 source_order = src_order 349 ) 350 dob_format = _cfg.get ( 351 group = 'XDT profile %s' % profile, 352 option = 'DOB format', 353 source_order = src_order 354 ) 355 if dob_format is None: 356 _log.warning('XDT profile [%s] does not define a date of birth format in <DOB format>' % profile) 357 bdt_files.append({'file': name, 'source': source, 'encoding': encoding, 'dob_format': dob_format}) 358 359 dtos = [] 360 for bdt_file in bdt_files: 361 try: 362 # FIXME: potentially return several persons per file 363 dto = gmPerson.get_person_from_xdt ( 364 filename = bdt_file['file'], 365 encoding = bdt_file['encoding'], 366 dob_format = bdt_file['dob_format'] 367 ) 368 369 except IOError: 370 gmGuiHelpers.gm_show_info ( 371 _( 372 'Cannot access BDT file\n\n' 373 ' [%s]\n\n' 374 'to import patient.\n\n' 375 'Please check your configuration.' 376 ) % bdt_file, 377 _('Activating xDT patient') 378 ) 379 _log.exception('cannot access xDT file [%s]' % bdt_file['file']) 380 continue 381 except: 382 gmGuiHelpers.gm_show_error ( 383 _( 384 'Cannot load patient from BDT file\n\n' 385 ' [%s]' 386 ) % bdt_file, 387 _('Activating xDT patient') 388 ) 389 _log.exception('cannot read patient from xDT file [%s]' % bdt_file['file']) 390 continue 391 392 dtos.append({'dto': dto, 'source': gmTools.coalesce(bdt_file['source'], dto.source)}) 393 394 return dtos
395 #============================================================
396 -def load_persons_from_pracsoft_au():
397 398 pracsoft_files = [] 399 400 # try detecting PATIENTS.IN files 401 candidates = [] 402 drives = 'cdefghijklmnopqrstuvwxyz' 403 for drive in drives: 404 candidate = drive + ':\MDW2\PATIENTS.IN' 405 candidates.extend(glob.glob(candidate)) 406 for candidate in candidates: 407 drive, filename = os.path.splitdrive(candidate) 408 pracsoft_files.append({'file': candidate, 'source': 'PracSoft (AU): drive %s' % drive}) 409 410 # add configured one(s) 411 src_order = [ 412 ('explicit', 'append'), 413 ('workbase', 'append'), 414 ('local', 'append'), 415 ('user', 'append'), 416 ('system', 'append') 417 ] 418 fnames = _cfg.get ( 419 group = 'AU PracSoft PATIENTS.IN', 420 option = 'filename', 421 source_order = src_order 422 ) 423 424 src_order = [ 425 ('explicit', 'return'), 426 ('user', 'return'), 427 ('system', 'return'), 428 ('local', 'return'), 429 ('workbase', 'return') 430 ] 431 source = _cfg.get ( 432 group = 'AU PracSoft PATIENTS.IN', 433 option = 'source', 434 source_order = src_order 435 ) 436 437 if source is not None: 438 for fname in fnames: 439 fname = os.path.abspath(os.path.expanduser(fname)) 440 if os.access(fname, os.R_OK): 441 pracsoft_files.append({'file': os.path.expanduser(fname), 'source': source}) 442 else: 443 _log.error('cannot read [%s] in AU PracSoft profile' % fname) 444 445 # and parse them 446 dtos = [] 447 for pracsoft_file in pracsoft_files: 448 try: 449 tmp = gmPerson.get_persons_from_pracsoft_file(filename = pracsoft_file['file']) 450 except: 451 _log.exception('cannot parse PracSoft file [%s]' % pracsoft_file['file']) 452 continue 453 for dto in tmp: 454 dtos.append({'dto': dto, 'source': pracsoft_file['source']}) 455 456 return dtos
457 #============================================================
458 -def load_persons_from_kvks():
459 460 dbcfg = gmCfg.cCfgSQL() 461 kvk_dir = os.path.abspath(os.path.expanduser(dbcfg.get2 ( 462 option = 'DE.KVK.spool_dir', 463 workplace = gmSurgery.gmCurrentPractice().active_workplace, 464 bias = 'workplace', 465 default = u'/var/spool/kvkd/' 466 ))) 467 dtos = [] 468 for dto in gmKVK.get_available_kvks_as_dtos(spool_dir = kvk_dir): 469 dtos.append({'dto': dto, 'source': 'KVK'}) 470 471 return dtos
472 #============================================================
473 -def get_person_from_external_sources(parent=None, search_immediately=False, activate_immediately=False):
474 """Load patient from external source. 475 476 - scan external sources for candidates 477 - let user select source 478 - if > 1 available: always 479 - if only 1 available: depending on search_immediately 480 - search for patients matching info from external source 481 - if more than one match: 482 - let user select patient 483 - if no match: 484 - create patient 485 - activate patient 486 """ 487 # get DTOs from interfaces 488 dtos = [] 489 dtos.extend(load_persons_from_xdt()) 490 dtos.extend(load_persons_from_pracsoft_au()) 491 dtos.extend(load_persons_from_kvks()) 492 493 # no external persons 494 if len(dtos) == 0: 495 gmDispatcher.send(signal='statustext', msg=_('No patients found in external sources.')) 496 return None 497 498 # one external patient with DOB - already active ? 499 if (len(dtos) == 1) and (dtos[0]['dto'].dob is not None): 500 dto = dtos[0]['dto'] 501 # is it already the current patient ? 502 curr_pat = gmPerson.gmCurrentPatient() 503 if curr_pat.connected: 504 key_dto = dto.firstnames + dto.lastnames + dto.dob.strftime('%Y-%m-%d') + dto.gender 505 names = curr_pat.get_active_name() 506 key_pat = names['firstnames'] + names['lastnames'] + curr_pat.get_formatted_dob(format = '%Y-%m-%d') + curr_pat['gender'] 507 _log.debug('current patient: %s' % key_pat) 508 _log.debug('dto patient : %s' % key_dto) 509 if key_dto == key_pat: 510 gmDispatcher.send(signal='statustext', msg=_('The only external patient is already active in GNUmed.'), beep=False) 511 return None 512 513 # one external person - look for internal match immediately ? 514 if (len(dtos) == 1) and search_immediately: 515 dto = dtos[0]['dto'] 516 517 # several external persons 518 else: 519 if parent is None: 520 parent = wx.GetApp().GetTopWindow() 521 dlg = cSelectPersonDTOFromListDlg(parent=parent, id=-1) 522 dlg.set_dtos(dtos=dtos) 523 result = dlg.ShowModal() 524 if result == wx.ID_CANCEL: 525 return None 526 dto = dlg.get_selected_dto()['dto'] 527 dlg.Destroy() 528 529 # search 530 idents = dto.get_candidate_identities(can_create=True) 531 if idents is None: 532 gmGuiHelpers.gm_show_info (_( 533 'Cannot create new patient:\n\n' 534 ' [%s %s (%s), %s]' 535 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())), 536 _('Activating external patient') 537 ) 538 return None 539 540 if len(idents) == 1: 541 ident = idents[0] 542 543 if len(idents) > 1: 544 if parent is None: 545 parent = wx.GetApp().GetTopWindow() 546 dlg = cSelectPersonFromListDlg(parent=parent, id=-1) 547 dlg.set_persons(persons=idents) 548 result = dlg.ShowModal() 549 if result == wx.ID_CANCEL: 550 return None 551 ident = dlg.get_selected_person() 552 dlg.Destroy() 553 554 if activate_immediately: 555 if not set_active_patient(patient = ident): 556 gmGuiHelpers.gm_show_info ( 557 _( 558 'Cannot activate patient:\n\n' 559 '%s %s (%s)\n' 560 '%s' 561 ) % (dto.firstnames, dto.lastnames, dto.gender, dto.dob.strftime('%x').decode(gmI18N.get_encoding())), 562 _('Activating external patient') 563 ) 564 return None 565 566 dto.import_extra_data(identity = ident) 567 dto.delete_from_source() 568 569 return ident
570 #============================================================
571 -class cPersonSearchCtrl(wx.TextCtrl):
572 """Widget for smart search for persons.""" 573
574 - def __init__(self, *args, **kwargs):
575 576 try: 577 kwargs['style'] = kwargs['style'] | wx.TE_PROCESS_ENTER 578 except KeyError: 579 kwargs['style'] = wx.TE_PROCESS_ENTER 580 581 # need to explicitly process ENTER events to avoid 582 # them being handed over to the next control 583 wx.TextCtrl.__init__(self, *args, **kwargs) 584 585 self.person = None 586 587 self.SetToolTipString (_( 588 'To search for a person type any of: \n' 589 '\n' 590 ' - fragment of last or first name\n' 591 " - date of birth (can start with '$' or '*')\n" 592 " - GNUmed ID of person (can start with '#')\n" 593 ' - exterenal ID of person\n' 594 '\n' 595 'and hit <ENTER>.\n' 596 '\n' 597 'Shortcuts:\n' 598 ' <F2>\n' 599 ' - scan external sources for persons\n' 600 ' <CURSOR-UP>\n' 601 ' - recall most recently used search term\n' 602 ' <CURSOR-DOWN>\n' 603 ' - list 10 most recently found persons\n' 604 )) 605 606 # FIXME: set query generator 607 self.__person_searcher = gmPerson.cPatientSearcher_SQL() 608 609 self._prev_search_term = None 610 self.__prev_idents = [] 611 self._lclick_count = 0 612 613 self._display_name() 614 615 self.__register_events()
616 #-------------------------------------------------------- 617 # utility methods 618 #--------------------------------------------------------
619 - def _display_name(self):
620 name = u'' 621 622 if self.person is not None: 623 name = self.person['description'] 624 625 self.SetValue(name)
626 #--------------------------------------------------------
627 - def _remember_ident(self, ident=None):
628 629 if not isinstance(ident, gmPerson.cIdentity): 630 return False 631 632 # only unique identities 633 for known_ident in self.__prev_idents: 634 if known_ident['pk_identity'] == ident['pk_identity']: 635 return True 636 637 self.__prev_idents.append(ident) 638 639 # and only 10 of them 640 if len(self.__prev_idents) > 10: 641 self.__prev_idents.pop(0) 642 643 return True
644 #-------------------------------------------------------- 645 # event handling 646 #--------------------------------------------------------
647 - def __register_events(self):
648 wx.EVT_CHAR(self, self.__on_char) 649 wx.EVT_SET_FOCUS(self, self._on_get_focus) 650 wx.EVT_KILL_FOCUS (self, self._on_loose_focus) 651 wx.EVT_TEXT_ENTER (self, self.GetId(), self.__on_enter)
652 #--------------------------------------------------------
653 - def _on_get_focus(self, evt):
654 """upon tabbing in 655 656 - select all text in the field so that the next 657 character typed will delete it 658 """ 659 wx.CallAfter(self.SetSelection, -1, -1) 660 evt.Skip()
661 #--------------------------------------------------------
662 - def _on_loose_focus(self, evt):
663 # - redraw the currently active name upon losing focus 664 665 # if we use wx.EVT_KILL_FOCUS we will also receive this event 666 # when closing our application or loosing focus to another 667 # application which is NOT what we intend to achieve, 668 # however, this is the least ugly way of doing this due to 669 # certain vagaries of wxPython (see the Wiki) 670 671 # just for good measure 672 wx.CallAfter(self.SetSelection, 0, 0) 673 674 self._display_name() 675 self._remember_ident(self.person) 676 677 evt.Skip()
678 #--------------------------------------------------------
679 - def __on_char(self, evt):
680 self._on_char(evt)
681
682 - def _on_char(self, evt):
683 """True: patient was selected. 684 False: no patient was selected. 685 """ 686 keycode = evt.GetKeyCode() 687 688 # list of previously active patients 689 if keycode == wx.WXK_DOWN: 690 evt.Skip() 691 if len(self.__prev_idents) == 0: 692 return False 693 694 dlg = cSelectPersonFromListDlg(parent = wx.GetTopLevelParent(self), id = -1) 695 dlg.set_persons(persons = self.__prev_idents) 696 result = dlg.ShowModal() 697 if result == wx.ID_OK: 698 wx.BeginBusyCursor() 699 self.person = dlg.get_selected_person() 700 self._display_name() 701 dlg.Destroy() 702 wx.EndBusyCursor() 703 return True 704 705 dlg.Destroy() 706 return False 707 708 # recall previous search fragment 709 if keycode == wx.WXK_UP: 710 evt.Skip() 711 # FIXME: cycling through previous fragments 712 if self._prev_search_term is not None: 713 self.SetValue(self._prev_search_term) 714 return False 715 716 # invoke external patient sources 717 if keycode == wx.WXK_F2: 718 evt.Skip() 719 dbcfg = gmCfg.cCfgSQL() 720 search_immediately = bool(dbcfg.get2 ( 721 option = 'patient_search.external_sources.immediately_search_if_single_source', 722 workplace = gmSurgery.gmCurrentPractice().active_workplace, 723 bias = 'user', 724 default = 0 725 )) 726 p = get_person_from_external_sources ( 727 parent = wx.GetTopLevelParent(self), 728 search_immediately = search_immediately 729 ) 730 if p is not None: 731 self.person = p 732 self._display_name() 733 return True 734 return False 735 736 # FIXME: invoke add new person 737 # FIXME: add popup menu apart from system one 738 739 evt.Skip()
740 #--------------------------------------------------------
741 - def __on_enter(self, evt):
742 """This is called from the ENTER handler.""" 743 744 # ENTER but no search term ? 745 curr_search_term = self.GetValue().strip() 746 if curr_search_term == '': 747 return None 748 749 # same person anywys ? 750 if self.person is not None: 751 if curr_search_term == self.person['description']: 752 return None 753 754 # remember search fragment 755 if self.IsModified(): 756 self._prev_search_term = curr_search_term 757 758 self._on_enter(search_term = curr_search_term)
759 #--------------------------------------------------------
760 - def _on_enter(self, search_term=None):
761 """This can be overridden in child classes.""" 762 763 wx.BeginBusyCursor() 764 765 # get list of matching ids 766 idents = self.__person_searcher.get_identities(search_term) 767 768 if idents is None: 769 wx.EndBusyCursor() 770 gmGuiHelpers.gm_show_info ( 771 _('Error searching for matching persons.\n\n' 772 'Search term: "%s"' 773 ) % search_term, 774 _('selecting person') 775 ) 776 return None 777 778 _log.info("%s matching person(s) found", len(idents)) 779 780 if len(idents) == 0: 781 wx.EndBusyCursor() 782 783 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 784 wx.GetTopLevelParent(self), 785 -1, 786 caption = _('Selecting patient'), 787 question = _( 788 'Cannot find any matching patients for the search term\n\n' 789 ' "%s"\n\n' 790 'You may want to try a shorter search term.\n' 791 ) % search_term, 792 button_defs = [ 793 {'label': _('Go back'), 'tooltip': _('Go back and search again.'), 'default': True}, 794 {'label': _('Create new'), 'tooltip': _('Create new patient.')} 795 ] 796 ) 797 if dlg.ShowModal() != wx.ID_NO: 798 return 799 800 wiz = gmDemographicsWidgets.cNewPatientWizard(parent = self.GetParent()) 801 result = wiz.RunWizard(activate = False) 802 if result is False: 803 return None 804 self.person = result 805 self._display_name() 806 return None 807 808 # only one matching identity 809 if len(idents) == 1: 810 self.person = idents[0] 811 self._display_name() # needed when the found patient is the same as the active one 812 wx.EndBusyCursor() 813 return None 814 815 # more than one matching identity: let user select from pick list 816 dlg = cSelectPersonFromListDlg(parent=wx.GetTopLevelParent(self), id=-1) 817 dlg.set_persons(persons=idents) 818 wx.EndBusyCursor() 819 result = dlg.ShowModal() 820 if result == wx.ID_CANCEL: 821 dlg.Destroy() 822 return None 823 824 wx.BeginBusyCursor() 825 self.person = dlg.get_selected_person() 826 dlg.Destroy() 827 self._display_name() # needed when the found patient is the same as the active one 828 wx.EndBusyCursor() 829 830 return None
831 #============================================================
832 -def set_active_patient(patient=None, forced_reload=False):
833 834 # warn if DOB is missing 835 try: 836 patient['dob'] 837 check_dob = True 838 except TypeError: 839 check_dob = False 840 841 if check_dob: 842 if patient['dob'] is None: 843 gmGuiHelpers.gm_show_warning ( 844 aTitle = _('Checking date of birth'), 845 aMessage = _( 846 '\n' 847 ' %s\n' 848 '\n' 849 'The date of birth for this patient is not known !\n' 850 '\n' 851 'You can proceed to work on the patient but\n' 852 'GNUmed will be unable to assist you with\n' 853 'age-related decisions.\n' 854 ) % patient['description_gender'] 855 ) 856 857 return gmPerson.set_active_patient(patient = patient, forced_reload = forced_reload)
858 #------------------------------------------------------------
859 -class cActivePatientSelector(cPersonSearchCtrl):
860
861 - def __init__ (self, *args, **kwargs):
862 863 cPersonSearchCtrl.__init__(self, *args, **kwargs) 864 865 selector_tooltip = _( 866 'Patient search field. \n' 867 '\n' 868 'To search, type any of:\n' 869 ' - fragment of last or first name\n' 870 " - date of birth (can start with '$' or '*')\n" 871 " - patient ID (can start with '#')\n" 872 'and hit <ENTER>.\n' 873 '\n' 874 '<CURSOR-UP>\n' 875 ' - recall most recently used search term\n' 876 '<CURSOR-DOWN>\n' 877 ' - list 10 most recently activated patients\n' 878 '<F2>\n' 879 ' - scan external sources for patients to import and activate\n' 880 ) 881 self.SetToolTip(wx.ToolTip(selector_tooltip)) 882 883 # get configuration 884 cfg = gmCfg.cCfgSQL() 885 886 self.__always_dismiss_on_search = bool ( 887 cfg.get2 ( 888 option = 'patient_search.always_dismiss_previous_patient', 889 workplace = gmSurgery.gmCurrentPractice().active_workplace, 890 bias = 'user', 891 default = 0 892 ) 893 ) 894 895 self.__always_reload_after_search = bool ( 896 cfg.get2 ( 897 option = 'patient_search.always_reload_new_patient', 898 workplace = gmSurgery.gmCurrentPractice().active_workplace, 899 bias = 'user', 900 default = 0 901 ) 902 ) 903 904 self.__register_events()
905 #-------------------------------------------------------- 906 # utility methods 907 #--------------------------------------------------------
908 - def _display_name(self):
909 name = u'' 910 911 curr_pat = gmPerson.gmCurrentPatient() 912 if curr_pat.connected: 913 name = curr_pat['description'] 914 if curr_pat.locked: 915 name = _('%(name)s (locked)') % {'name': name} 916 917 self.SetValue(name)
918 #--------------------------------------------------------
919 - def _set_person_as_active_patient(self, pat):
920 if not set_active_patient(patient=pat, forced_reload = self.__always_reload_after_search): 921 _log.error('cannot change active patient') 922 return None 923 924 self._remember_ident(pat) 925 926 dbcfg = gmCfg.cCfgSQL() 927 dob_distance = dbcfg.get2 ( 928 option = u'patient_search.dob_warn_interval', 929 workplace = gmSurgery.gmCurrentPractice().active_workplace, 930 bias = u'user', 931 default = u'1 week' 932 ) 933 934 if pat.dob_in_range(dob_distance, dob_distance): 935 now = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone) 936 enc = gmI18N.get_encoding() 937 gmDispatcher.send(signal = 'statustext', msg = _( 938 '%(pat)s turns %(age)s on %(month)s %(day)s ! (today is %(month_now)s %(day_now)s)') % { 939 'pat': pat.get_description_gender(), 940 'age': pat.get_medical_age().strip('y'), 941 'month': pat.get_formatted_dob(format = '%B', encoding = enc), 942 'day': pat.get_formatted_dob(format = '%d', encoding = enc), 943 'month_now': now.strftime('%B').decode(enc), 944 'day_now': now.strftime('%d') 945 } 946 ) 947 948 return True
949 #-------------------------------------------------------- 950 # event handling 951 #--------------------------------------------------------
952 - def __register_events(self):
953 # client internal signals 954 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 955 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_name_identity_change) 956 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_name_identity_change) 957 958 gmDispatcher.connect(signal = 'patient_locked', receiver = self._on_post_patient_selection) 959 gmDispatcher.connect(signal = 'patient_unlocked', receiver = self._on_post_patient_selection)
960 #----------------------------------------------
961 - def _on_name_identity_change(self, **kwargs):
962 wx.CallAfter(self._display_name)
963 #----------------------------------------------
964 - def _on_post_patient_selection(self, **kwargs):
965 if gmPerson.gmCurrentPatient().connected: 966 self.person = gmPerson.gmCurrentPatient().patient 967 else: 968 self.person = None 969 wx.CallAfter(self._display_name)
970 #----------------------------------------------
971 - def _on_enter(self, search_term = None):
972 973 if self.__always_dismiss_on_search: 974 _log.warning("dismissing patient before patient search") 975 self._set_person_as_active_patient(-1) 976 977 super(self.__class__, self)._on_enter(search_term=search_term) 978 979 if self.person is None: 980 return 981 982 self._set_person_as_active_patient(self.person) 983 self._display_name()
984 #----------------------------------------------
985 - def _on_char(self, evt):
986 987 success = super(self.__class__, self)._on_char(evt) 988 if success: 989 self._set_person_as_active_patient(self.person)
990 #============================================================ 991 # waiting list widgets 992 #============================================================
993 -class cWaitingZonePhraseWheel(gmPhraseWheel.cPhraseWheel):
994
995 - def __init__(self, *args, **kwargs):
996 997 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 998 999 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = []) 1000 mp.setThresholds(1, 2, 2) 1001 self.matcher = mp 1002 self.selection_only = False
1003 1004 #--------------------------------------------------------
1005 - def update_matcher(self, items):
1006 self.matcher.set_items([ {'data': i, 'label': i, 'weight': 1} for i in items ])
1007 1008 #============================================================ 1009 from Gnumed.wxGladeWidgets import wxgWaitingListEntryEditAreaPnl 1010
1011 -class cWaitingListEntryEditAreaPnl(wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1012
1013 - def __init__ (self, *args, **kwargs):
1014 1015 try: 1016 self.patient = kwargs['patient'] 1017 del kwargs['patient'] 1018 except KeyError: 1019 self.patient = None 1020 1021 try: 1022 data = kwargs['entry'] 1023 del kwargs['entry'] 1024 except KeyError: 1025 data = None 1026 1027 wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl.__init__(self, *args, **kwargs) 1028 gmEditArea.cGenericEditAreaMixin.__init__(self) 1029 1030 if data is None: 1031 self.mode = 'new' 1032 else: 1033 self.data = data 1034 self.mode = 'edit' 1035 1036 praxis = gmSurgery.gmCurrentPractice() 1037 pats = praxis.waiting_list_patients 1038 zones = {} 1039 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ]) 1040 self._PRW_zone.update_matcher(items = zones.keys())
1041 #-------------------------------------------------------- 1042 # edit area mixin API 1043 #--------------------------------------------------------
1044 - def _refresh_as_new(self):
1045 if self.patient is None: 1046 self._PRW_patient.person = None 1047 self._PRW_patient.Enable(True) 1048 self._PRW_patient.SetFocus() 1049 else: 1050 self._PRW_patient.person = self.patient 1051 self._PRW_patient.Enable(False) 1052 self._PRW_comment.SetFocus() 1053 self._PRW_patient._display_name() 1054 1055 self._PRW_comment.SetValue(u'') 1056 self._PRW_zone.SetValue(u'') 1057 self._SPCTRL_urgency.SetValue(0)
1058 #--------------------------------------------------------
1059 - def _refresh_from_existing(self):
1060 self._PRW_patient.person = gmPerson.cIdentity(aPK_obj = self.data['pk_identity']) 1061 self._PRW_patient.Enable(False) 1062 self._PRW_patient._display_name() 1063 1064 self._PRW_comment.SetValue(gmTools.coalesce(self.data['comment'], u'')) 1065 self._PRW_zone.SetValue(gmTools.coalesce(self.data['waiting_zone'], u'')) 1066 self._SPCTRL_urgency.SetValue(self.data['urgency']) 1067 1068 self._PRW_comment.SetFocus()
1069 #--------------------------------------------------------
1070 - def _valid_for_save(self):
1071 validity = True 1072 1073 self.display_tctrl_as_valid(tctrl = self._PRW_patient, valid = (self._PRW_patient.person is not None)) 1074 validity = (self._PRW_patient.person is not None) 1075 1076 if validity is False: 1077 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add to waiting list. Missing essential input.')) 1078 1079 return validity
1080 #----------------------------------------------------------------
1081 - def _save_as_new(self):
1082 # FIXME: filter out dupes 1083 self._PRW_patient.person.put_on_waiting_list ( 1084 urgency = self._SPCTRL_urgency.GetValue(), 1085 comment = gmTools.none_if(self._PRW_comment.GetValue().strip(), u''), 1086 zone = gmTools.none_if(self._PRW_zone.GetValue().strip(), u'') 1087 ) 1088 # dummy: 1089 self.data = {'pk_identity': None, 'comment': None, 'waiting_zone': None, 'urgency': 0} 1090 return True
1091 #----------------------------------------------------------------
1092 - def _save_as_update(self):
1093 gmSurgery.gmCurrentPractice().update_in_waiting_list ( 1094 pk = self.data['pk_waiting_list'], 1095 urgency = self._SPCTRL_urgency.GetValue(), 1096 comment = self._PRW_comment.GetValue().strip(), 1097 zone = self._PRW_zone.GetValue().strip() 1098 ) 1099 return True
1100 #============================================================ 1101 from Gnumed.wxGladeWidgets import wxgWaitingListPnl 1102
1103 -class cWaitingListPnl(wxgWaitingListPnl.wxgWaitingListPnl, gmRegetMixin.cRegetOnPaintMixin):
1104
1105 - def __init__ (self, *args, **kwargs):
1106 1107 wxgWaitingListPnl.wxgWaitingListPnl.__init__(self, *args, **kwargs) 1108 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1109 1110 self.__current_zone = None 1111 1112 self.__init_ui() 1113 self.__register_events()
1114 #-------------------------------------------------------- 1115 # interal helpers 1116 #--------------------------------------------------------
1117 - def __init_ui(self):
1118 self._LCTRL_patients.set_columns ([ 1119 _('Zone'), 1120 _('Urgency'), 1121 #' ! ', 1122 _('Waiting time'), 1123 _('Patient'), 1124 _('Born'), 1125 _('Comment') 1126 ]) 1127 self._LCTRL_patients.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 1128 self._PRW_zone.add_callback_on_selection(callback = self._on_zone_selected) 1129 self._PRW_zone.add_callback_on_lose_focus(callback = self._on_zone_selected)
1130 #--------------------------------------------------------
1131 - def __register_events(self):
1132 gmDispatcher.connect(signal = u'waiting_list_generic_mod_db', receiver = self._on_waiting_list_modified)
1133 #--------------------------------------------------------
1134 - def __refresh_waiting_list(self):
1135 1136 praxis = gmSurgery.gmCurrentPractice() 1137 pats = praxis.waiting_list_patients 1138 1139 # set matcher to all zones currently in use 1140 zones = {} 1141 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ]) 1142 self._PRW_zone.update_matcher(items = zones.keys()) 1143 del zones 1144 1145 # filter patient list by zone and set waiting list 1146 self.__current_zone = self._PRW_zone.GetValue().strip() 1147 if self.__current_zone == u'': 1148 pats = [ p for p in pats ] 1149 else: 1150 pats = [ p for p in pats if p['waiting_zone'] == self.__current_zone ] 1151 1152 self._LCTRL_patients.set_string_items ( 1153 [ [ 1154 gmTools.coalesce(p['waiting_zone'], u''), 1155 p['urgency'], 1156 p['waiting_time_formatted'].replace(u'00 ', u'', 1).replace('00:', u'').lstrip('0'), 1157 u'%s, %s (%s)' % (p['lastnames'], p['firstnames'], p['l10n_gender']), 1158 p['dob'].strftime('%x').decode(gmI18N.get_encoding()), 1159 gmTools.coalesce(p['comment'], u'') 1160 ] for p in pats 1161 ] 1162 ) 1163 self._LCTRL_patients.set_column_widths() 1164 self._LCTRL_patients.set_data(pats) 1165 self._LCTRL_patients.Refresh() 1166 self._LCTRL_patients.SetToolTipString ( _( 1167 '%s patients are waiting.\n' 1168 '\n' 1169 'Doubleclick to activate (entry will stay in list).' 1170 ) % len(pats)) 1171 1172 self._LBL_no_of_patients.SetLabel(_('(%s patients)') % len(pats)) 1173 1174 if len(pats) == 0: 1175 self._BTN_activate.Enable(False) 1176 self._BTN_activateplus.Enable(False) 1177 self._BTN_remove.Enable(False) 1178 self._BTN_edit.Enable(False) 1179 self._BTN_up.Enable(False) 1180 self._BTN_down.Enable(False) 1181 else: 1182 self._BTN_activate.Enable(True) 1183 self._BTN_activateplus.Enable(True) 1184 self._BTN_remove.Enable(True) 1185 self._BTN_edit.Enable(True) 1186 if len(pats) > 1: 1187 self._BTN_up.Enable(True) 1188 self._BTN_down.Enable(True)
1189 #-------------------------------------------------------- 1190 # event handlers 1191 #--------------------------------------------------------
1192 - def _on_zone_selected(self, zone=None):
1193 if self.__current_zone == self._PRW_zone.GetValue().strip(): 1194 return True 1195 wx.CallAfter(self.__refresh_waiting_list) 1196 return True
1197 #--------------------------------------------------------
1198 - def _on_waiting_list_modified(self, *args, **kwargs):
1199 wx.CallAfter(self._schedule_data_reget)
1200 #--------------------------------------------------------
1201 - def _on_list_item_activated(self, evt):
1202 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1203 if item is None: 1204 return 1205 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1206 wx.CallAfter(set_active_patient, patient = pat)
1207 #--------------------------------------------------------
1208 - def _on_activate_button_pressed(self, evt):
1209 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1210 if item is None: 1211 return 1212 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1213 wx.CallAfter(set_active_patient, patient = pat)
1214 #--------------------------------------------------------
1215 - def _on_activateplus_button_pressed(self, evt):
1216 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1217 if item is None: 1218 return 1219 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1220 gmSurgery.gmCurrentPractice().remove_from_waiting_list(pk = item['pk_waiting_list']) 1221 wx.CallAfter(set_active_patient, patient = pat)
1222 #--------------------------------------------------------
1223 - def _on_add_patient_button_pressed(self, evt):
1224 1225 curr_pat = gmPerson.gmCurrentPatient() 1226 if not curr_pat.connected: 1227 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add waiting list entry: No patient selected.'), beep = True) 1228 return 1229 1230 ea = cWaitingListEntryEditAreaPnl(self, -1, patient = curr_pat) 1231 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1232 dlg.ShowModal() 1233 dlg.Destroy()
1234 #--------------------------------------------------------
1235 - def _on_edit_button_pressed(self, event):
1236 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1237 if item is None: 1238 return 1239 ea = cWaitingListEntryEditAreaPnl(self, -1, entry = item) 1240 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1241 dlg.ShowModal() 1242 dlg.Destroy()
1243 #--------------------------------------------------------
1244 - def _on_remove_button_pressed(self, evt):
1245 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1246 if item is None: 1247 return 1248 gmSurgery.gmCurrentPractice().remove_from_waiting_list(pk = item['pk_waiting_list'])
1249 #--------------------------------------------------------
1250 - def _on_up_button_pressed(self, evt):
1251 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1252 if item is None: 1253 return 1254 gmSurgery.gmCurrentPractice().raise_in_waiting_list(current_position = item['list_position'])
1255 #--------------------------------------------------------
1256 - def _on_down_button_pressed(self, evt):
1257 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1258 if item is None: 1259 return 1260 gmSurgery.gmCurrentPractice().lower_in_waiting_list(current_position = item['list_position'])
1261 #-------------------------------------------------------- 1262 # edit 1263 #-------------------------------------------------------- 1264 # reget-on-paint API 1265 #--------------------------------------------------------
1266 - def _populate_with_data(self):
1267 self.__refresh_waiting_list() 1268 return True
1269 #============================================================ 1270 # main 1271 #------------------------------------------------------------ 1272 if __name__ == "__main__": 1273 1274 if len(sys.argv) > 1: 1275 if sys.argv[1] == 'test': 1276 gmI18N.activate_locale() 1277 gmI18N.install_domain() 1278 1279 app = wx.PyWidgetTester(size = (200, 40)) 1280 # app.SetWidget(cSelectPersonFromListDlg, -1) 1281 # app.SetWidget(cPersonSearchCtrl, -1) 1282 # app.SetWidget(cActivePatientSelector, -1) 1283 app.SetWidget(cWaitingListPnl, -1) 1284 app.MainLoop() 1285 1286 #============================================================ 1287 # docs 1288 #------------------------------------------------------------ 1289 # functionality 1290 # ------------- 1291 # - hitting ENTER on non-empty field (and more than threshold chars) 1292 # - start search 1293 # - display results in a list, prefixed with numbers 1294 # - last name 1295 # - first name 1296 # - gender 1297 # - age 1298 # - city + street (no ZIP, no number) 1299 # - last visit (highlighted if within a certain interval) 1300 # - arbitrary marker (e.g. office attendance this quartal, missing KVK, appointments, due dates) 1301 # - if none found -> go to entry of new patient 1302 # - scrolling in this list 1303 # - ENTER selects patient 1304 # - ESC cancels selection 1305 # - number selects patient 1306 # 1307 # - hitting cursor-up/-down 1308 # - cycle through history of last 10 search fragments 1309 # 1310 # - hitting alt-L = List, alt-P = previous 1311 # - show list of previous ten patients prefixed with numbers 1312 # - scrolling in list 1313 # - ENTER selects patient 1314 # - ESC cancels selection 1315 # - number selects patient 1316 # 1317 # - hitting ALT-N 1318 # - immediately goes to entry of new patient 1319 # 1320 # - hitting cursor-right in a patient selection list 1321 # - pops up more detail about the patient 1322 # - ESC/cursor-left goes back to list 1323 # 1324 # - hitting TAB 1325 # - makes sure the currently active patient is displayed 1326 1327 #------------------------------------------------------------ 1328 # samples 1329 # ------- 1330 # working: 1331 # Ian Haywood 1332 # Haywood Ian 1333 # Haywood 1334 # Amador Jimenez (yes, two last names but no hyphen: Spain, for example) 1335 # Ian Haywood 19/12/1977 1336 # 19/12/1977 1337 # 19-12-1977 1338 # 19.12.1977 1339 # 19771219 1340 # $dob 1341 # *dob 1342 # #ID 1343 # ID 1344 # HIlbert, karsten 1345 # karsten, hilbert 1346 # kars, hilb 1347 # 1348 # non-working: 1349 # Haywood, Ian <40 1350 # ?, Ian 1977 1351 # Ian Haywood, 19/12/77 1352 # PUPIC 1353 # "hilb; karsten, 23.10.74" 1354 1355 #------------------------------------------------------------ 1356 # notes 1357 # ----- 1358 # >> 3. There are countries in which people have more than one 1359 # >> (significant) lastname (spanish-speaking countries are one case :), some 1360 # >> asian countries might be another one). 1361 # -> we need per-country query generators ... 1362 1363 # search case sensitive by default, switch to insensitive if not found ? 1364 1365 # accent insensitive search: 1366 # select * from * where to_ascii(column, 'encoding') like '%test%'; 1367 # may not work with Unicode 1368 1369 # phrase wheel is most likely too slow 1370 1371 # extend search fragment history 1372 1373 # ask user whether to send off level 3 queries - or thread them 1374 1375 # we don't expect patient IDs in complicated patterns, hence any digits signify a date 1376 1377 # FIXME: make list window fit list size ... 1378 1379 # clear search field upon get-focus ? 1380 1381 # F1 -> context help with hotkey listing 1382 1383 # th -> th|t 1384 # v/f/ph -> f|v|ph 1385 # maybe don't do umlaut translation in the first 2-3 letters 1386 # such that not to defeat index use for the first level query ? 1387 1388 # user defined function key to start search 1389 1390 #============================================================ 1391 # $Log: gmPatSearchWidgets.py,v $ 1392 # Revision 1.130 2009/12/21 15:12:29 ncq 1393 # - cleanup 1394 # - fix typo 1395 # - missing return 1396 # 1397 # Revision 1.129 2009/11/15 01:10:34 ncq 1398 # - cleanup 1399 # 1400 # Revision 1.128 2009/07/17 09:25:06 ncq 1401 # - ! -> Urgency as per list 1402 # - adding acts on the current patient *only* 1403 # - add missing Destroy 1404 # 1405 # Revision 1.127 2009/07/02 20:56:26 ncq 1406 # - used edit area dlg2 1407 # 1408 # Revision 1.126 2009/07/01 17:10:35 ncq 1409 # - need to return state from set_active_patient 1410 # 1411 # Revision 1.125 2009/06/20 12:47:17 ncq 1412 # - only display last encounter in search results if 1413 # patient has clinical data (that is, is a patient) 1414 # 1415 # Revision 1.124 2009/06/04 16:27:47 ncq 1416 # - add set active patient and use it 1417 # - adjust to dob-less persons 1418 # 1419 # Revision 1.123 2009/04/21 17:00:00 ncq 1420 # - edit area dlg now takes single_entry argument 1421 # 1422 # Revision 1.122 2009/02/05 14:30:36 ncq 1423 # - only run new-patient-wizard if user explicitely said so 1424 # - do not try to set active patient if user cancelled new patient wizard 1425 # 1426 # Revision 1.121 2009/02/04 12:35:18 ncq 1427 # - support editing waiting list entries 1428 # 1429 # Revision 1.120 2009/01/30 12:11:43 ncq 1430 # - waiting list entry edit area 1431 # 1432 # Revision 1.119 2009/01/22 11:16:41 ncq 1433 # - implement moving waiting list entries 1434 # 1435 # Revision 1.118 2009/01/21 22:39:02 ncq 1436 # - waiting zones phrasewheel and use it 1437 # 1438 # Revision 1.117 2009/01/21 18:04:41 ncq 1439 # - implement most of waiting list 1440 # 1441 # Revision 1.116 2009/01/17 23:08:31 ncq 1442 # - waiting list 1443 # 1444 # Revision 1.115 2008/12/17 21:59:22 ncq 1445 # - add support for merging patients 1446 # 1447 # Revision 1.114 2008/12/09 23:43:27 ncq 1448 # - use description_gender 1449 # - no more hardcoded plugin raising after patient activation 1450 # 1451 # Revision 1.113 2008/10/12 16:26:46 ncq 1452 # - cleanup 1453 # 1454 # Revision 1.112 2008/09/01 20:28:51 ncq 1455 # - properly handle case when several option sources define AU PracSoft source 1456 # 1457 # Revision 1.111 2008/08/28 18:34:18 ncq 1458 # - make active patient selector react to patient activation, 1459 # name/identity change all by itself with updating its display, 1460 # don't let top panel do it for us 1461 # 1462 # Revision 1.110 2008/07/28 20:27:20 ncq 1463 # - do not try to activate None person 1464 # 1465 # Revision 1.109 2008/07/07 13:43:17 ncq 1466 # - current patient .connected 1467 # 1468 # Revision 1.108 2008/05/13 14:13:57 ncq 1469 # - fix on-focus-select-all behaviour 1470 # - don't display search term after name - when a search failed this gets confusing 1471 # 1472 # Revision 1.107 2008/04/16 20:39:39 ncq 1473 # - working versions of the wxGlade code and use it, too 1474 # - show client version in login dialog 1475 # 1476 # Revision 1.106 2008/03/20 15:31:59 ncq 1477 # - missing \n added 1478 # 1479 # Revision 1.105 2008/03/09 20:18:22 ncq 1480 # - cleanup 1481 # - load_patient_* -> get_person_* 1482 # - make cPatientSelector() generic -> cPersonSearchCtrl() 1483 # 1484 # Revision 1.104 2008/02/25 17:40:18 ncq 1485 # - new style logging 1486 # 1487 # Revision 1.103 2008/01/30 14:09:39 ncq 1488 # - switch to new style cfg file support 1489 # - cleanup 1490 # 1491 # Revision 1.102 2008/01/27 21:17:49 ncq 1492 # - improve message on patient not found 1493 # 1494 # Revision 1.101 2008/01/22 12:24:55 ncq 1495 # - include search fragment into patient name display 1496 # - reenable on kill focus handler restoring patient name 1497 # - improved wording on patient not found 1498 # 1499 # Revision 1.100 2008/01/11 16:15:33 ncq 1500 # - first/last -> first-/lastnames 1501 # 1502 # Revision 1.99 2008/01/05 16:41:27 ncq 1503 # - remove logging from gm_show_*() 1504 # 1505 # Revision 1.98 2007/12/11 12:49:26 ncq 1506 # - explicit signal handling 1507 # 1508 # Revision 1.97 2007/11/12 23:05:55 ncq 1509 # - import extra data from DTOs 1510 # 1511 # Revision 1.96 2007/11/10 20:58:59 ncq 1512 # - use dto.get_candidate_identities() and dto.delete_from_source() 1513 # 1514 # Revision 1.95 2007/10/19 12:52:34 ncq 1515 # - implement search_immediately in load_patient_from_external_source() 1516 # 1517 # Revision 1.94 2007/10/12 14:20:09 ncq 1518 # - prepare "activate_immediately" in load_patient_from_external_sources() 1519 # 1520 # Revision 1.93 2007/10/12 13:33:06 ncq 1521 # - if only one external patient available - activate it right away 1522 # 1523 # Revision 1.92 2007/10/11 12:15:09 ncq 1524 # - make filling patient selector list more robust in absence of match_type field 1525 # 1526 # Revision 1.91 2007/10/07 12:32:42 ncq 1527 # - workplace property now on gmSurgery.gmCurrentPractice() borg 1528 # 1529 # Revision 1.90 2007/09/10 12:38:12 ncq 1530 # - improve wording on announcing upcoming patient birthday 1531 # 1532 # Revision 1.89 2007/08/28 14:18:13 ncq 1533 # - no more gm_statustext() 1534 # 1535 # Revision 1.88 2007/08/12 00:12:41 ncq 1536 # - no more gmSignals.py 1537 # 1538 # Revision 1.87 2007/07/17 16:00:28 ncq 1539 # - check existence of PracSoft import file 1540 # 1541 # Revision 1.86 2007/07/11 21:11:08 ncq 1542 # - display patient locked state 1543 # - listen on patient lock/unlock events 1544 # 1545 # Revision 1.85 2007/07/09 12:46:33 ncq 1546 # - move cDataMiningPnl to gmDataMiningWidgets.py 1547 # 1548 # Revision 1.84 2007/07/07 12:43:25 ncq 1549 # - in cDataMiningPnl use cPatientListingCtrl 1550 # 1551 # Revision 1.83 2007/06/28 12:40:48 ncq 1552 # - handle dto.dob being optional now 1553 # - support dto source gotten from xdt file 1554 # 1555 # Revision 1.82 2007/06/12 16:03:58 ncq 1556 # - some comments 1557 # - fix typo 1558 # - better error display on failing queries 1559 # 1560 # Revision 1.81 2007/06/10 10:12:55 ncq 1561 # - options need names 1562 # 1563 # Revision 1.80 2007/05/18 15:55:58 ncq 1564 # - auto-select first item in person/dto selector 1565 # 1566 # Revision 1.79 2007/05/14 14:56:41 ncq 1567 # - fix typo 1568 # 1569 # Revision 1.78 2007/05/14 13:52:24 ncq 1570 # - add display_name() in two places to fix visual glitch with search 1571 # 1572 # Revision 1.77 2007/05/14 13:37:42 ncq 1573 # - don't do anything if the only external patient is 1574 # already the active patient in GNUmed 1575 # 1576 # Revision 1.76 2007/05/14 13:11:24 ncq 1577 # - use statustext() signal 1578 # 1579 # Revision 1.75 2007/05/07 08:04:36 ncq 1580 # - a bit of cleanup 1581 # 1582 # Revision 1.74 2007/04/19 13:13:47 ncq 1583 # - cleanup 1584 # 1585 # Revision 1.73 2007/04/11 14:53:33 ncq 1586 # - do some safeguarding against binary/large files being dropped onto 1587 # the data mining plugin - check mimetype and size 1588 # 1589 # Revision 1.72 2007/04/09 22:03:57 ncq 1590 # - make data mining panel a file drop target 1591 # 1592 # Revision 1.71 2007/04/09 21:12:49 ncq 1593 # - better wording in contribute email 1594 # - properly unicode() SQL results 1595 # 1596 # Revision 1.70 2007/04/09 18:52:47 ncq 1597 # - magic patient activation from report result list 1598 # 1599 # Revision 1.69 2007/04/09 16:31:06 ncq 1600 # - add _on_contribute 1601 # 1602 # Revision 1.68 2007/04/08 21:17:14 ncq 1603 # - add more event handlers to data mining panel 1604 # 1605 # Revision 1.67 2007/04/07 22:45:28 ncq 1606 # - add save handler to data mining panel 1607 # 1608 # Revision 1.66 2007/04/06 23:15:21 ncq 1609 # - add data mining panel 1610 # 1611 # Revision 1.65 2007/04/01 15:29:51 ncq 1612 # - safely get_encoding() 1613 # 1614 # Revision 1.64 2007/03/02 15:38:47 ncq 1615 # - decode() strftime() to u'' 1616 # 1617 # Revision 1.63 2007/02/22 17:41:13 ncq 1618 # - adjust to gmPerson changes 1619 # 1620 # Revision 1.62 2007/02/17 14:01:26 ncq 1621 # - gmCurrentProvider.workplace now property 1622 # - notify about birthday after activating patient 1623 # - remove crufty code/docs 1624 # 1625 # Revision 1.61 2007/02/15 14:58:08 ncq 1626 # - tie KVKs intoi external patient sources framework 1627 # 1628 # Revision 1.60 2007/02/13 17:07:38 ncq 1629 # - tie PracSoft PATIENTS.IN file into external patients framework 1630 # - *always* let user decide on whether to activate an external patient 1631 # even if only a single source provides a patient 1632 # 1633 # Revision 1.59 2007/01/20 22:52:27 ncq 1634 # - .KeyCode -> GetKeyCode() 1635 # 1636 # Revision 1.58 2007/01/18 22:07:52 ncq 1637 # - (Get)KeyCode() -> KeyCode so 2.8 can do 1638 # 1639 # Revision 1.57 2007/01/10 23:04:12 ncq 1640 # - support explicit DOB format for xDT files 1641 # 1642 # Revision 1.56 2006/12/13 14:57:16 ncq 1643 # - inform about no patients found in external sources 1644 # 1645 # Revision 1.55 2006/11/24 14:23:19 ncq 1646 # - self.Close() does not need wx.ID_* 1647 # 1648 # Revision 1.54 2006/11/24 09:56:03 ncq 1649 # - improved message when error searching patient 1650 # 1651 # Revision 1.53 2006/11/20 19:11:04 ncq 1652 # - improved message when no matching patient found 1653 # 1654 # Revision 1.52 2006/11/20 17:05:55 ncq 1655 # - do not search if supposed search term matches 'description' of current patient 1656 # 1657 # Revision 1.51 2006/11/01 12:54:40 ncq 1658 # - there may not be a previous encounter so don't try to 1659 # format it's start date if so 1660 # 1661 # Revision 1.50 2006/10/31 12:43:09 ncq 1662 # - out with the crap 1663 # - no more patient expanders 1664 # 1665 # Revision 1.49 2006/10/30 16:46:52 ncq 1666 # - missing encoding in xDT source defs does not *have* to be 1667 # an error as the file itself may contain the encoding itself 1668 # 1669 # Revision 1.48 2006/10/28 14:57:17 ncq 1670 # - use cPatient.get_last_encounter() 1671 # 1672 # Revision 1.47 2006/10/28 12:34:53 ncq 1673 # - make person and dto selector dialogs handle functionality themselves 1674 # - remove person selector panel class 1675 # - act on ENTER/double-click in person/dto select list 1676 # 1677 # Revision 1.46 2006/10/25 07:46:44 ncq 1678 # - Format() -> strftime() since datetime.datetime does not have .Format() 1679 # 1680 # Revision 1.45 2006/10/24 13:26:43 ncq 1681 # - switch to gmPG2 1682 # 1683 # Revision 1.44 2006/09/13 07:55:11 ncq 1684 # - handle encoding in xDT patient sources 1685 # 1686 # Revision 1.43 2006/09/06 07:22:34 ncq 1687 # - add missing import for glob module 1688 # 1689 # Revision 1.42 2006/09/01 14:46:30 ncq 1690 # - add (untested) MCS/Isynet external patient source 1691 # 1692 # Revision 1.41 2006/08/09 15:00:47 ncq 1693 # - better search widget tooltip 1694 # 1695 # Revision 1.40 2006/07/30 18:48:18 ncq 1696 # - invoke load_external_patient on <F2> in searcher 1697 # - robustify by commenting out shaky KVK code 1698 # 1699 # Revision 1.39 2006/07/30 17:51:00 ncq 1700 # - cleanup 1701 # 1702 # Revision 1.38 2006/07/27 17:07:18 ncq 1703 # - cleanup 1704 # - make Cursor-Down the way to invoke previous patients 1705 # 1706 # Revision 1.37 2006/07/26 13:22:37 ncq 1707 # - degrade non-fatal error messages to info messages 1708 # 1709 # Revision 1.36 2006/07/26 13:15:03 ncq 1710 # - cleanup 1711 # 1712 # Revision 1.35 2006/07/24 19:38:39 ncq 1713 # - fix "prev patients" list (alt-p) in patient selector 1714 # - start obsoleting old (ugly) patient pick list 1715 # 1716 # Revision 1.34 2006/07/24 14:18:31 ncq 1717 # - finish pat/dto selection dialogs 1718 # - use them in loading external patients and selecting among matches in search control 1719 # 1720 # Revision 1.33 2006/07/24 11:31:11 ncq 1721 # - cleanup 1722 # - add dialogs to select person/person-dto from list 1723 # - use dto-selection dialog when loading external patient 1724 # 1725 # Revision 1.32 2006/07/22 15:18:24 ncq 1726 # - better error logging 1727 # 1728 # Revision 1.31 2006/07/21 14:48:39 ncq 1729 # - proper returns from load_patient_from_external_sources() 1730 # 1731 # Revision 1.30 2006/07/19 21:41:13 ncq 1732 # - support list of xdt files 1733 # 1734 # Revision 1.29 2006/07/18 21:18:13 ncq 1735 # - add proper load_patient_from_external_sources() 1736 # 1737 # Revision 1.28 2006/05/15 13:36:00 ncq 1738 # - signal cleanup: 1739 # - activating_patient -> pre_patient_selection 1740 # - patient_selected -> post_patient_selection 1741 # 1742 # Revision 1.27 2006/05/12 12:18:11 ncq 1743 # - whoami -> whereami cleanup 1744 # - use gmCurrentProvider() 1745 # 1746 # Revision 1.26 2006/05/04 09:49:20 ncq 1747 # - get_clinical_record() -> get_emr() 1748 # - adjust to changes in set_active_patient() 1749 # - need explicit set_active_patient() after ask_for_patient() if wanted 1750 # 1751 # Revision 1.25 2005/12/14 17:01:51 ncq 1752 # - use improved db cfg option getting 1753 # 1754 # Revision 1.24 2005/09/28 21:27:30 ncq 1755 # - a lot of wx2.6-ification 1756 # 1757 # Revision 1.23 2005/09/27 20:44:59 ncq 1758 # - wx.wx* -> wx.* 1759 # 1760 # Revision 1.22 2005/09/26 18:01:51 ncq 1761 # - use proper way to import wx26 vs wx2.4 1762 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES 1763 # - time for fixup 1764 # 1765 # Revision 1.21 2005/09/24 09:17:29 ncq 1766 # - some wx2.6 compatibility fixes 1767 # 1768 # Revision 1.20 2005/09/12 15:18:05 ncq 1769 # - fix faulty call to SetActivePatient() found by Richard when using 1770 # always_dismiss_after_search 1771 # 1772 # Revision 1.19 2005/09/11 17:35:05 ncq 1773 # - support "patient_search.always_reload_new_patient" 1774 # 1775 # Revision 1.18 2005/09/04 07:31:14 ncq 1776 # - Richard requested the "no active patient" tag be removed 1777 # when no patient is active 1778 # 1779 # Revision 1.17 2005/05/05 06:29:22 ncq 1780 # - if patient not found invoke new patient wizard with activate=true 1781 # 1782 # Revision 1.16 2005/03/08 16:54:13 ncq 1783 # - teach patient picklist about cIdentity 1784 # 1785 # Revision 1.15 2005/02/20 10:33:26 sjtan 1786 # 1787 # disable lose focus to prevent core dumping in a wxPython version. 1788 # 1789 # Revision 1.14 2005/02/13 15:28:07 ncq 1790 # - v_basic_person.i_pk -> pk_identity 1791 # 1792 # Revision 1.13 2005/02/12 13:59:11 ncq 1793 # - v_basic_person.i_id -> i_pk 1794 # 1795 # Revision 1.12 2005/02/01 10:16:07 ihaywood 1796 # refactoring of gmDemographicRecord and follow-on changes as discussed. 1797 # 1798 # gmTopPanel moves to gmHorstSpace 1799 # gmRichardSpace added -- example code at present, haven't even run it myself 1800 # (waiting on some icon .pngs from Richard) 1801 # 1802 # Revision 1.11 2005/01/31 10:37:26 ncq 1803 # - gmPatient.py -> gmPerson.py 1804 # 1805 # Revision 1.10 2004/10/20 12:40:55 ncq 1806 # - some cleanup 1807 # 1808 # Revision 1.9 2004/10/20 07:49:45 sjtan 1809 # small forward wxWidget compatibility change. 1810 # 1811 # Revision 1.7 2004/09/06 22:22:15 ncq 1812 # - properly use setDBParam() 1813 # 1814 # Revision 1.6 2004/09/02 00:40:13 ncq 1815 # - store option always_dismiss_previous_patient if not found 1816 # 1817 # Revision 1.5 2004/09/01 22:04:03 ncq 1818 # - cleanup 1819 # - code order change to avoid exception due to None-check after logging 1820 # 1821 # Revision 1.4 2004/08/29 23:15:58 ncq 1822 # - Richard improved the patient picklist popup 1823 # - plus cleanup/fixes etc 1824 # 1825 # Revision 1.3 2004/08/24 15:41:13 ncq 1826 # - eventually force patient pick list to stay on top 1827 # as suggested by Robin Dunn 1828 # 1829 # Revision 1.2 2004/08/20 13:31:05 ncq 1830 # - cleanup/improve comments/improve naming 1831 # - dismiss patient regardless of search result if so configured 1832 # - don't search on empty search term 1833 # 1834 # Revision 1.1 2004/08/20 06:46:38 ncq 1835 # - used to be gmPatientSelector.py 1836 # 1837 # Revision 1.45 2004/08/19 13:59:14 ncq 1838 # - streamline/cleanup 1839 # - Busy Cursor according to Richard 1840 # 1841 # Revision 1.44 2004/08/18 08:18:35 ncq 1842 # - later wxWidgets version don't support parent=NULL anymore 1843 # 1844 # Revision 1.43 2004/08/02 18:53:36 ncq 1845 # - used wx.Begin/EndBusyCursor() around setting the active patient 1846 # 1847 # Revision 1.42 2004/07/18 19:51:12 ncq 1848 # - cleanup, use True/False, not true/false 1849 # - use run_ro_query(), not run_query() 1850 # 1851 # Revision 1.41 2004/07/15 20:36:11 ncq 1852 # - better default size 1853 # 1854 # Revision 1.40 2004/06/20 16:01:05 ncq 1855 # - please epydoc more carefully 1856 # 1857 # Revision 1.39 2004/06/20 06:49:21 ihaywood 1858 # changes required due to Epydoc's OCD 1859 # 1860 # Revision 1.38 2004/06/04 16:27:12 shilbert 1861 # - giving focus highlights the text and lets you replace it 1862 # 1863 # Revision 1.37 2004/03/27 18:24:11 ncq 1864 # - Ian and I fixed the same bugs again :) 1865 # 1866 # Revision 1.36 2004/03/27 04:37:01 ihaywood 1867 # lnk_person2address now lnk_person_org_address 1868 # sundry bugfixes 1869 # 1870 # Revision 1.35 2004/03/25 11:03:23 ncq 1871 # - getActiveName -> get_names 1872 # 1873 # Revision 1.34 2004/03/20 19:48:07 ncq 1874 # - adapt to flat id list from get_patient_ids 1875 # 1876 # Revision 1.33 2004/03/12 13:23:41 ncq 1877 # - cleanup 1878 # 1879 # Revision 1.32 2004/03/05 11:22:35 ncq 1880 # - import from Gnumed.<pkg> 1881 # 1882 # Revision 1.31 2004/03/04 19:47:06 ncq 1883 # - switch to package based import: from Gnumed.foo import bar 1884 # 1885 # Revision 1.30 2004/02/25 09:46:22 ncq 1886 # - import from pycommon now, not python-common 1887 # 1888 # Revision 1.29 2004/02/05 18:41:31 ncq 1889 # - make _on_patient_selected() thread-safe 1890 # - move SetActivePatient() logic into gmPatient 1891 # 1892 # Revision 1.28 2004/02/04 00:55:02 ncq 1893 # - moved UI-independant patient searching code into business/gmPatient.py where it belongs 1894 # 1895 # Revision 1.27 2003/11/22 14:49:32 ncq 1896 # - fix typo 1897 # 1898 # Revision 1.26 2003/11/22 00:26:10 ihaywood 1899 # Set coding to latin-1 to please python 2.3 1900 # 1901 # Revision 1.25 2003/11/18 23:34:02 ncq 1902 # - don't use reload to force reload of same patient 1903 # 1904 # Revision 1.24 2003/11/17 10:56:38 sjtan 1905 # 1906 # synced and commiting. 1907 # 1908 # Revision 1.23 2003/11/09 17:29:22 shilbert 1909 # - ['demographics'] -> ['demographic record'] 1910 # 1911 # Revision 1.22 2003/11/07 20:44:11 ncq 1912 # - some cleanup 1913 # - listen to patient_selected by other widgets 1914 # 1915 # Revision 1.21 2003/11/04 00:22:46 ncq 1916 # - remove unneeded import 1917 # 1918 # Revision 1.20 2003/10/26 17:42:51 ncq 1919 # - cleanup 1920 # 1921 # Revision 1.19 2003/10/26 11:27:10 ihaywood 1922 # gmPatient is now the "patient stub", all demographics stuff in gmDemographics. 1923 # 1924 # Ergregious breakages are fixed, but needs more work 1925 # 1926 # Revision 1.18 2003/10/26 01:36:13 ncq 1927 # - gmTmpPatient -> gmPatient 1928 # 1929 # Revision 1.17 2003/10/19 12:17:57 ncq 1930 # - typo fix 1931 # 1932 # Revision 1.16 2003/09/21 07:52:57 ihaywood 1933 # those bloody umlauts killed by python interpreter! 1934 # 1935 # Revision 1.15 2003/07/07 08:34:31 ihaywood 1936 # bugfixes on gmdrugs.sql for postgres 7.3 1937 # 1938 # Revision 1.14 2003/07/03 15:22:19 ncq 1939 # - removed unused stuff 1940 # 1941 # Revision 1.13 2003/06/29 14:08:02 ncq 1942 # - extra ; removed 1943 # - kvk/incoming/ as default KVK dir 1944 # 1945 # Revision 1.12 2003/04/09 16:20:19 ncq 1946 # - added set selection on get focus -- but we don't tab in yet !! 1947 # - can now set title on pick list 1948 # - added KVK handling :-) 1949 # 1950 # Revision 1.11 2003/04/04 23:54:30 ncq 1951 # - tweaked some parent and style settings here and there, but still 1952 # not where we want to be with the pick list ... 1953 # 1954 # Revision 1.10 2003/04/04 20:46:45 ncq 1955 # - adapt to new gmCurrentPatient() 1956 # - add (ugly) tooltip 1957 # - break out helper _display_name() 1958 # - fix KeyError on ids[0] 1959 # 1960 # Revision 1.9 2003/04/01 16:01:06 ncq 1961 # - fixed handling of no-patients-found result 1962 # 1963 # Revision 1.8 2003/04/01 15:33:22 ncq 1964 # - and double :: of course, duh 1965 # 1966 # Revision 1.7 2003/04/01 15:32:52 ncq 1967 # - stupid indentation error 1968 # 1969 # Revision 1.6 2003/04/01 12:28:14 ncq 1970 # - factored out _normalize_soundalikes() 1971 # 1972 # Revision 1.5 2003/04/01 09:08:27 ncq 1973 # - better Umlaut replacement 1974 # - safer cursor.close() handling 1975 # 1976 # Revision 1.4 2003/03/31 23:38:16 ncq 1977 # - sensitize() helper for smart names upcasing 1978 # - massively rework queries for speedup 1979 # 1980 # Revision 1.3 2003/03/30 00:24:00 ncq 1981 # - typos 1982 # - (hopefully) less confusing printk()s at startup 1983 # 1984 # Revision 1.2 2003/03/28 15:56:04 ncq 1985 # - adapted to GnuMed CVS structure 1986 # 1987