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.131 2010/01/31 18:19:41 ncq Exp $ 
  14  __version__ = "$Revision: 1.131 $" 
  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 name = _('<type here to search patient>') 911 912 curr_pat = gmPerson.gmCurrentPatient() 913 if curr_pat.connected: 914 name = curr_pat['description'] 915 if curr_pat.locked: 916 name = _('%(name)s (locked)') % {'name': name} 917 918 self.SetValue(name)
919 #--------------------------------------------------------
920 - def _set_person_as_active_patient(self, pat):
921 if not set_active_patient(patient=pat, forced_reload = self.__always_reload_after_search): 922 _log.error('cannot change active patient') 923 return None 924 925 self._remember_ident(pat) 926 927 dbcfg = gmCfg.cCfgSQL() 928 dob_distance = dbcfg.get2 ( 929 option = u'patient_search.dob_warn_interval', 930 workplace = gmSurgery.gmCurrentPractice().active_workplace, 931 bias = u'user', 932 default = u'1 week' 933 ) 934 935 if pat.dob_in_range(dob_distance, dob_distance): 936 now = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone) 937 enc = gmI18N.get_encoding() 938 gmDispatcher.send(signal = 'statustext', msg = _( 939 '%(pat)s turns %(age)s on %(month)s %(day)s ! (today is %(month_now)s %(day_now)s)') % { 940 'pat': pat.get_description_gender(), 941 'age': pat.get_medical_age().strip('y'), 942 'month': pat.get_formatted_dob(format = '%B', encoding = enc), 943 'day': pat.get_formatted_dob(format = '%d', encoding = enc), 944 'month_now': now.strftime('%B').decode(enc), 945 'day_now': now.strftime('%d') 946 } 947 ) 948 949 return True
950 #-------------------------------------------------------- 951 # event handling 952 #--------------------------------------------------------
953 - def __register_events(self):
954 # client internal signals 955 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 956 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_name_identity_change) 957 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_name_identity_change) 958 959 gmDispatcher.connect(signal = 'patient_locked', receiver = self._on_post_patient_selection) 960 gmDispatcher.connect(signal = 'patient_unlocked', receiver = self._on_post_patient_selection)
961 #----------------------------------------------
962 - def _on_name_identity_change(self, **kwargs):
963 wx.CallAfter(self._display_name)
964 #----------------------------------------------
965 - def _on_post_patient_selection(self, **kwargs):
966 if gmPerson.gmCurrentPatient().connected: 967 self.person = gmPerson.gmCurrentPatient().patient 968 else: 969 self.person = None 970 wx.CallAfter(self._display_name)
971 #----------------------------------------------
972 - def _on_enter(self, search_term = None):
973 974 if self.__always_dismiss_on_search: 975 _log.warning("dismissing patient before patient search") 976 self._set_person_as_active_patient(-1) 977 978 super(self.__class__, self)._on_enter(search_term=search_term) 979 980 if self.person is None: 981 return 982 983 self._set_person_as_active_patient(self.person) 984 self._display_name()
985 #----------------------------------------------
986 - def _on_char(self, evt):
987 988 success = super(self.__class__, self)._on_char(evt) 989 if success: 990 self._set_person_as_active_patient(self.person)
991 #============================================================ 992 # waiting list widgets 993 #============================================================
994 -class cWaitingZonePhraseWheel(gmPhraseWheel.cPhraseWheel):
995
996 - def __init__(self, *args, **kwargs):
997 998 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 999 1000 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = []) 1001 mp.setThresholds(1, 2, 2) 1002 self.matcher = mp 1003 self.selection_only = False
1004 1005 #--------------------------------------------------------
1006 - def update_matcher(self, items):
1007 self.matcher.set_items([ {'data': i, 'label': i, 'weight': 1} for i in items ])
1008 1009 #============================================================ 1010 from Gnumed.wxGladeWidgets import wxgWaitingListEntryEditAreaPnl 1011
1012 -class cWaitingListEntryEditAreaPnl(wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1013
1014 - def __init__ (self, *args, **kwargs):
1015 1016 try: 1017 self.patient = kwargs['patient'] 1018 del kwargs['patient'] 1019 except KeyError: 1020 self.patient = None 1021 1022 try: 1023 data = kwargs['entry'] 1024 del kwargs['entry'] 1025 except KeyError: 1026 data = None 1027 1028 wxgWaitingListEntryEditAreaPnl.wxgWaitingListEntryEditAreaPnl.__init__(self, *args, **kwargs) 1029 gmEditArea.cGenericEditAreaMixin.__init__(self) 1030 1031 if data is None: 1032 self.mode = 'new' 1033 else: 1034 self.data = data 1035 self.mode = 'edit' 1036 1037 praxis = gmSurgery.gmCurrentPractice() 1038 pats = praxis.waiting_list_patients 1039 zones = {} 1040 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ]) 1041 self._PRW_zone.update_matcher(items = zones.keys())
1042 #-------------------------------------------------------- 1043 # edit area mixin API 1044 #--------------------------------------------------------
1045 - def _refresh_as_new(self):
1046 if self.patient is None: 1047 self._PRW_patient.person = None 1048 self._PRW_patient.Enable(True) 1049 self._PRW_patient.SetFocus() 1050 else: 1051 self._PRW_patient.person = self.patient 1052 self._PRW_patient.Enable(False) 1053 self._PRW_comment.SetFocus() 1054 self._PRW_patient._display_name() 1055 1056 self._PRW_comment.SetValue(u'') 1057 self._PRW_zone.SetValue(u'') 1058 self._SPCTRL_urgency.SetValue(0)
1059 #--------------------------------------------------------
1060 - def _refresh_from_existing(self):
1061 self._PRW_patient.person = gmPerson.cIdentity(aPK_obj = self.data['pk_identity']) 1062 self._PRW_patient.Enable(False) 1063 self._PRW_patient._display_name() 1064 1065 self._PRW_comment.SetValue(gmTools.coalesce(self.data['comment'], u'')) 1066 self._PRW_zone.SetValue(gmTools.coalesce(self.data['waiting_zone'], u'')) 1067 self._SPCTRL_urgency.SetValue(self.data['urgency']) 1068 1069 self._PRW_comment.SetFocus()
1070 #--------------------------------------------------------
1071 - def _valid_for_save(self):
1072 validity = True 1073 1074 self.display_tctrl_as_valid(tctrl = self._PRW_patient, valid = (self._PRW_patient.person is not None)) 1075 validity = (self._PRW_patient.person is not None) 1076 1077 if validity is False: 1078 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add to waiting list. Missing essential input.')) 1079 1080 return validity
1081 #----------------------------------------------------------------
1082 - def _save_as_new(self):
1083 # FIXME: filter out dupes 1084 self._PRW_patient.person.put_on_waiting_list ( 1085 urgency = self._SPCTRL_urgency.GetValue(), 1086 comment = gmTools.none_if(self._PRW_comment.GetValue().strip(), u''), 1087 zone = gmTools.none_if(self._PRW_zone.GetValue().strip(), u'') 1088 ) 1089 # dummy: 1090 self.data = {'pk_identity': None, 'comment': None, 'waiting_zone': None, 'urgency': 0} 1091 return True
1092 #----------------------------------------------------------------
1093 - def _save_as_update(self):
1094 gmSurgery.gmCurrentPractice().update_in_waiting_list ( 1095 pk = self.data['pk_waiting_list'], 1096 urgency = self._SPCTRL_urgency.GetValue(), 1097 comment = self._PRW_comment.GetValue().strip(), 1098 zone = self._PRW_zone.GetValue().strip() 1099 ) 1100 return True
1101 #============================================================ 1102 from Gnumed.wxGladeWidgets import wxgWaitingListPnl 1103
1104 -class cWaitingListPnl(wxgWaitingListPnl.wxgWaitingListPnl, gmRegetMixin.cRegetOnPaintMixin):
1105
1106 - def __init__ (self, *args, **kwargs):
1107 1108 wxgWaitingListPnl.wxgWaitingListPnl.__init__(self, *args, **kwargs) 1109 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1110 1111 self.__current_zone = None 1112 1113 self.__init_ui() 1114 self.__register_events()
1115 #-------------------------------------------------------- 1116 # interal helpers 1117 #--------------------------------------------------------
1118 - def __init_ui(self):
1119 self._LCTRL_patients.set_columns ([ 1120 _('Zone'), 1121 _('Urgency'), 1122 #' ! ', 1123 _('Waiting time'), 1124 _('Patient'), 1125 _('Born'), 1126 _('Comment') 1127 ]) 1128 self._LCTRL_patients.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 1129 self._PRW_zone.add_callback_on_selection(callback = self._on_zone_selected) 1130 self._PRW_zone.add_callback_on_lose_focus(callback = self._on_zone_selected)
1131 #--------------------------------------------------------
1132 - def __register_events(self):
1133 gmDispatcher.connect(signal = u'waiting_list_generic_mod_db', receiver = self._on_waiting_list_modified)
1134 #--------------------------------------------------------
1135 - def __refresh_waiting_list(self):
1136 1137 praxis = gmSurgery.gmCurrentPractice() 1138 pats = praxis.waiting_list_patients 1139 1140 # set matcher to all zones currently in use 1141 zones = {} 1142 zones.update([ [p['waiting_zone'], None] for p in pats if p['waiting_zone'] is not None ]) 1143 self._PRW_zone.update_matcher(items = zones.keys()) 1144 del zones 1145 1146 # filter patient list by zone and set waiting list 1147 self.__current_zone = self._PRW_zone.GetValue().strip() 1148 if self.__current_zone == u'': 1149 pats = [ p for p in pats ] 1150 else: 1151 pats = [ p for p in pats if p['waiting_zone'] == self.__current_zone ] 1152 1153 self._LCTRL_patients.set_string_items ( 1154 [ [ 1155 gmTools.coalesce(p['waiting_zone'], u''), 1156 p['urgency'], 1157 p['waiting_time_formatted'].replace(u'00 ', u'', 1).replace('00:', u'').lstrip('0'), 1158 u'%s, %s (%s)' % (p['lastnames'], p['firstnames'], p['l10n_gender']), 1159 p['dob'].strftime('%x').decode(gmI18N.get_encoding()), 1160 gmTools.coalesce(p['comment'], u'') 1161 ] for p in pats 1162 ] 1163 ) 1164 self._LCTRL_patients.set_column_widths() 1165 self._LCTRL_patients.set_data(pats) 1166 self._LCTRL_patients.Refresh() 1167 self._LCTRL_patients.SetToolTipString ( _( 1168 '%s patients are waiting.\n' 1169 '\n' 1170 'Doubleclick to activate (entry will stay in list).' 1171 ) % len(pats)) 1172 1173 self._LBL_no_of_patients.SetLabel(_('(%s patients)') % len(pats)) 1174 1175 if len(pats) == 0: 1176 self._BTN_activate.Enable(False) 1177 self._BTN_activateplus.Enable(False) 1178 self._BTN_remove.Enable(False) 1179 self._BTN_edit.Enable(False) 1180 self._BTN_up.Enable(False) 1181 self._BTN_down.Enable(False) 1182 else: 1183 self._BTN_activate.Enable(True) 1184 self._BTN_activateplus.Enable(True) 1185 self._BTN_remove.Enable(True) 1186 self._BTN_edit.Enable(True) 1187 if len(pats) > 1: 1188 self._BTN_up.Enable(True) 1189 self._BTN_down.Enable(True)
1190 #-------------------------------------------------------- 1191 # event handlers 1192 #--------------------------------------------------------
1193 - def _on_zone_selected(self, zone=None):
1194 if self.__current_zone == self._PRW_zone.GetValue().strip(): 1195 return True 1196 wx.CallAfter(self.__refresh_waiting_list) 1197 return True
1198 #--------------------------------------------------------
1199 - def _on_waiting_list_modified(self, *args, **kwargs):
1200 wx.CallAfter(self._schedule_data_reget)
1201 #--------------------------------------------------------
1202 - def _on_list_item_activated(self, evt):
1203 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1204 if item is None: 1205 return 1206 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1207 wx.CallAfter(set_active_patient, patient = pat)
1208 #--------------------------------------------------------
1209 - def _on_activate_button_pressed(self, evt):
1210 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1211 if item is None: 1212 return 1213 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1214 wx.CallAfter(set_active_patient, patient = pat)
1215 #--------------------------------------------------------
1216 - def _on_activateplus_button_pressed(self, evt):
1217 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1218 if item is None: 1219 return 1220 pat = gmPerson.cIdentity(aPK_obj = item['pk_identity']) 1221 gmSurgery.gmCurrentPractice().remove_from_waiting_list(pk = item['pk_waiting_list']) 1222 wx.CallAfter(set_active_patient, patient = pat)
1223 #--------------------------------------------------------
1224 - def _on_add_patient_button_pressed(self, evt):
1225 1226 curr_pat = gmPerson.gmCurrentPatient() 1227 if not curr_pat.connected: 1228 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add waiting list entry: No patient selected.'), beep = True) 1229 return 1230 1231 ea = cWaitingListEntryEditAreaPnl(self, -1, patient = curr_pat) 1232 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1233 dlg.ShowModal() 1234 dlg.Destroy()
1235 #--------------------------------------------------------
1236 - def _on_edit_button_pressed(self, event):
1237 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1238 if item is None: 1239 return 1240 ea = cWaitingListEntryEditAreaPnl(self, -1, entry = item) 1241 dlg = gmEditArea.cGenericEditAreaDlg2(self, -1, edit_area = ea, single_entry = True) 1242 dlg.ShowModal() 1243 dlg.Destroy()
1244 #--------------------------------------------------------
1245 - def _on_remove_button_pressed(self, evt):
1246 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1247 if item is None: 1248 return 1249 gmSurgery.gmCurrentPractice().remove_from_waiting_list(pk = item['pk_waiting_list'])
1250 #--------------------------------------------------------
1251 - def _on_up_button_pressed(self, evt):
1252 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1253 if item is None: 1254 return 1255 gmSurgery.gmCurrentPractice().raise_in_waiting_list(current_position = item['list_position'])
1256 #--------------------------------------------------------
1257 - def _on_down_button_pressed(self, evt):
1258 item = self._LCTRL_patients.get_selected_item_data(only_one=True) 1259 if item is None: 1260 return 1261 gmSurgery.gmCurrentPractice().lower_in_waiting_list(current_position = item['list_position'])
1262 #-------------------------------------------------------- 1263 # edit 1264 #-------------------------------------------------------- 1265 # reget-on-paint API 1266 #--------------------------------------------------------
1267 - def _populate_with_data(self):
1268 self.__refresh_waiting_list() 1269 return True
1270 #============================================================ 1271 # main 1272 #------------------------------------------------------------ 1273 if __name__ == "__main__": 1274 1275 if len(sys.argv) > 1: 1276 if sys.argv[1] == 'test': 1277 gmI18N.activate_locale() 1278 gmI18N.install_domain() 1279 1280 app = wx.PyWidgetTester(size = (200, 40)) 1281 # app.SetWidget(cSelectPersonFromListDlg, -1) 1282 # app.SetWidget(cPersonSearchCtrl, -1) 1283 # app.SetWidget(cActivePatientSelector, -1) 1284 app.SetWidget(cWaitingListPnl, -1) 1285 app.MainLoop() 1286 1287 #============================================================ 1288 # docs 1289 #------------------------------------------------------------ 1290 # functionality 1291 # ------------- 1292 # - hitting ENTER on non-empty field (and more than threshold chars) 1293 # - start search 1294 # - display results in a list, prefixed with numbers 1295 # - last name 1296 # - first name 1297 # - gender 1298 # - age 1299 # - city + street (no ZIP, no number) 1300 # - last visit (highlighted if within a certain interval) 1301 # - arbitrary marker (e.g. office attendance this quartal, missing KVK, appointments, due dates) 1302 # - if none found -> go to entry of new patient 1303 # - scrolling in this list 1304 # - ENTER selects patient 1305 # - ESC cancels selection 1306 # - number selects patient 1307 # 1308 # - hitting cursor-up/-down 1309 # - cycle through history of last 10 search fragments 1310 # 1311 # - hitting alt-L = List, alt-P = previous 1312 # - show list of previous ten patients prefixed with numbers 1313 # - scrolling in list 1314 # - ENTER selects patient 1315 # - ESC cancels selection 1316 # - number selects patient 1317 # 1318 # - hitting ALT-N 1319 # - immediately goes to entry of new patient 1320 # 1321 # - hitting cursor-right in a patient selection list 1322 # - pops up more detail about the patient 1323 # - ESC/cursor-left goes back to list 1324 # 1325 # - hitting TAB 1326 # - makes sure the currently active patient is displayed 1327 1328 #------------------------------------------------------------ 1329 # samples 1330 # ------- 1331 # working: 1332 # Ian Haywood 1333 # Haywood Ian 1334 # Haywood 1335 # Amador Jimenez (yes, two last names but no hyphen: Spain, for example) 1336 # Ian Haywood 19/12/1977 1337 # 19/12/1977 1338 # 19-12-1977 1339 # 19.12.1977 1340 # 19771219 1341 # $dob 1342 # *dob 1343 # #ID 1344 # ID 1345 # HIlbert, karsten 1346 # karsten, hilbert 1347 # kars, hilb 1348 # 1349 # non-working: 1350 # Haywood, Ian <40 1351 # ?, Ian 1977 1352 # Ian Haywood, 19/12/77 1353 # PUPIC 1354 # "hilb; karsten, 23.10.74" 1355 1356 #------------------------------------------------------------ 1357 # notes 1358 # ----- 1359 # >> 3. There are countries in which people have more than one 1360 # >> (significant) lastname (spanish-speaking countries are one case :), some 1361 # >> asian countries might be another one). 1362 # -> we need per-country query generators ... 1363 1364 # search case sensitive by default, switch to insensitive if not found ? 1365 1366 # accent insensitive search: 1367 # select * from * where to_ascii(column, 'encoding') like '%test%'; 1368 # may not work with Unicode 1369 1370 # phrase wheel is most likely too slow 1371 1372 # extend search fragment history 1373 1374 # ask user whether to send off level 3 queries - or thread them 1375 1376 # we don't expect patient IDs in complicated patterns, hence any digits signify a date 1377 1378 # FIXME: make list window fit list size ... 1379 1380 # clear search field upon get-focus ? 1381 1382 # F1 -> context help with hotkey listing 1383 1384 # th -> th|t 1385 # v/f/ph -> f|v|ph 1386 # maybe don't do umlaut translation in the first 2-3 letters 1387 # such that not to defeat index use for the first level query ? 1388 1389 # user defined function key to start search 1390 1391 #============================================================ 1392 # $Log: gmPatSearchWidgets.py,v $ 1393 # Revision 1.131 2010/01/31 18:19:41 ncq 1394 # - show hint when no patient selected 1395 # 1396 # Revision 1.130 2009/12/21 15:12:29 ncq 1397 # - cleanup 1398 # - fix typo 1399 # - missing return 1400 # 1401 # Revision 1.129 2009/11/15 01:10:34 ncq 1402 # - cleanup 1403 # 1404 # Revision 1.128 2009/07/17 09:25:06 ncq 1405 # - ! -> Urgency as per list 1406 # - adding acts on the current patient *only* 1407 # - add missing Destroy 1408 # 1409 # Revision 1.127 2009/07/02 20:56:26 ncq 1410 # - used edit area dlg2 1411 # 1412 # Revision 1.126 2009/07/01 17:10:35 ncq 1413 # - need to return state from set_active_patient 1414 # 1415 # Revision 1.125 2009/06/20 12:47:17 ncq 1416 # - only display last encounter in search results if 1417 # patient has clinical data (that is, is a patient) 1418 # 1419 # Revision 1.124 2009/06/04 16:27:47 ncq 1420 # - add set active patient and use it 1421 # - adjust to dob-less persons 1422 # 1423 # Revision 1.123 2009/04/21 17:00:00 ncq 1424 # - edit area dlg now takes single_entry argument 1425 # 1426 # Revision 1.122 2009/02/05 14:30:36 ncq 1427 # - only run new-patient-wizard if user explicitely said so 1428 # - do not try to set active patient if user cancelled new patient wizard 1429 # 1430 # Revision 1.121 2009/02/04 12:35:18 ncq 1431 # - support editing waiting list entries 1432 # 1433 # Revision 1.120 2009/01/30 12:11:43 ncq 1434 # - waiting list entry edit area 1435 # 1436 # Revision 1.119 2009/01/22 11:16:41 ncq 1437 # - implement moving waiting list entries 1438 # 1439 # Revision 1.118 2009/01/21 22:39:02 ncq 1440 # - waiting zones phrasewheel and use it 1441 # 1442 # Revision 1.117 2009/01/21 18:04:41 ncq 1443 # - implement most of waiting list 1444 # 1445 # Revision 1.116 2009/01/17 23:08:31 ncq 1446 # - waiting list 1447 # 1448 # Revision 1.115 2008/12/17 21:59:22 ncq 1449 # - add support for merging patients 1450 # 1451 # Revision 1.114 2008/12/09 23:43:27 ncq 1452 # - use description_gender 1453 # - no more hardcoded plugin raising after patient activation 1454 # 1455 # Revision 1.113 2008/10/12 16:26:46 ncq 1456 # - cleanup 1457 # 1458 # Revision 1.112 2008/09/01 20:28:51 ncq 1459 # - properly handle case when several option sources define AU PracSoft source 1460 # 1461 # Revision 1.111 2008/08/28 18:34:18 ncq 1462 # - make active patient selector react to patient activation, 1463 # name/identity change all by itself with updating its display, 1464 # don't let top panel do it for us 1465 # 1466 # Revision 1.110 2008/07/28 20:27:20 ncq 1467 # - do not try to activate None person 1468 # 1469 # Revision 1.109 2008/07/07 13:43:17 ncq 1470 # - current patient .connected 1471 # 1472 # Revision 1.108 2008/05/13 14:13:57 ncq 1473 # - fix on-focus-select-all behaviour 1474 # - don't display search term after name - when a search failed this gets confusing 1475 # 1476 # Revision 1.107 2008/04/16 20:39:39 ncq 1477 # - working versions of the wxGlade code and use it, too 1478 # - show client version in login dialog 1479 # 1480 # Revision 1.106 2008/03/20 15:31:59 ncq 1481 # - missing \n added 1482 # 1483 # Revision 1.105 2008/03/09 20:18:22 ncq 1484 # - cleanup 1485 # - load_patient_* -> get_person_* 1486 # - make cPatientSelector() generic -> cPersonSearchCtrl() 1487 # 1488 # Revision 1.104 2008/02/25 17:40:18 ncq 1489 # - new style logging 1490 # 1491 # Revision 1.103 2008/01/30 14:09:39 ncq 1492 # - switch to new style cfg file support 1493 # - cleanup 1494 # 1495 # Revision 1.102 2008/01/27 21:17:49 ncq 1496 # - improve message on patient not found 1497 # 1498 # Revision 1.101 2008/01/22 12:24:55 ncq 1499 # - include search fragment into patient name display 1500 # - reenable on kill focus handler restoring patient name 1501 # - improved wording on patient not found 1502 # 1503 # Revision 1.100 2008/01/11 16:15:33 ncq 1504 # - first/last -> first-/lastnames 1505 # 1506 # Revision 1.99 2008/01/05 16:41:27 ncq 1507 # - remove logging from gm_show_*() 1508 # 1509 # Revision 1.98 2007/12/11 12:49:26 ncq 1510 # - explicit signal handling 1511 # 1512 # Revision 1.97 2007/11/12 23:05:55 ncq 1513 # - import extra data from DTOs 1514 # 1515 # Revision 1.96 2007/11/10 20:58:59 ncq 1516 # - use dto.get_candidate_identities() and dto.delete_from_source() 1517 # 1518 # Revision 1.95 2007/10/19 12:52:34 ncq 1519 # - implement search_immediately in load_patient_from_external_source() 1520 # 1521 # Revision 1.94 2007/10/12 14:20:09 ncq 1522 # - prepare "activate_immediately" in load_patient_from_external_sources() 1523 # 1524 # Revision 1.93 2007/10/12 13:33:06 ncq 1525 # - if only one external patient available - activate it right away 1526 # 1527 # Revision 1.92 2007/10/11 12:15:09 ncq 1528 # - make filling patient selector list more robust in absence of match_type field 1529 # 1530 # Revision 1.91 2007/10/07 12:32:42 ncq 1531 # - workplace property now on gmSurgery.gmCurrentPractice() borg 1532 # 1533 # Revision 1.90 2007/09/10 12:38:12 ncq 1534 # - improve wording on announcing upcoming patient birthday 1535 # 1536 # Revision 1.89 2007/08/28 14:18:13 ncq 1537 # - no more gm_statustext() 1538 # 1539 # Revision 1.88 2007/08/12 00:12:41 ncq 1540 # - no more gmSignals.py 1541 # 1542 # Revision 1.87 2007/07/17 16:00:28 ncq 1543 # - check existence of PracSoft import file 1544 # 1545 # Revision 1.86 2007/07/11 21:11:08 ncq 1546 # - display patient locked state 1547 # - listen on patient lock/unlock events 1548 # 1549 # Revision 1.85 2007/07/09 12:46:33 ncq 1550 # - move cDataMiningPnl to gmDataMiningWidgets.py 1551 # 1552 # Revision 1.84 2007/07/07 12:43:25 ncq 1553 # - in cDataMiningPnl use cPatientListingCtrl 1554 # 1555 # Revision 1.83 2007/06/28 12:40:48 ncq 1556 # - handle dto.dob being optional now 1557 # - support dto source gotten from xdt file 1558 # 1559 # Revision 1.82 2007/06/12 16:03:58 ncq 1560 # - some comments 1561 # - fix typo 1562 # - better error display on failing queries 1563 # 1564 # Revision 1.81 2007/06/10 10:12:55 ncq 1565 # - options need names 1566 # 1567 # Revision 1.80 2007/05/18 15:55:58 ncq 1568 # - auto-select first item in person/dto selector 1569 # 1570 # Revision 1.79 2007/05/14 14:56:41 ncq 1571 # - fix typo 1572 # 1573 # Revision 1.78 2007/05/14 13:52:24 ncq 1574 # - add display_name() in two places to fix visual glitch with search 1575 # 1576 # Revision 1.77 2007/05/14 13:37:42 ncq 1577 # - don't do anything if the only external patient is 1578 # already the active patient in GNUmed 1579 # 1580 # Revision 1.76 2007/05/14 13:11:24 ncq 1581 # - use statustext() signal 1582 # 1583 # Revision 1.75 2007/05/07 08:04:36 ncq 1584 # - a bit of cleanup 1585 # 1586 # Revision 1.74 2007/04/19 13:13:47 ncq 1587 # - cleanup 1588 # 1589 # Revision 1.73 2007/04/11 14:53:33 ncq 1590 # - do some safeguarding against binary/large files being dropped onto 1591 # the data mining plugin - check mimetype and size 1592 # 1593 # Revision 1.72 2007/04/09 22:03:57 ncq 1594 # - make data mining panel a file drop target 1595 # 1596 # Revision 1.71 2007/04/09 21:12:49 ncq 1597 # - better wording in contribute email 1598 # - properly unicode() SQL results 1599 # 1600 # Revision 1.70 2007/04/09 18:52:47 ncq 1601 # - magic patient activation from report result list 1602 # 1603 # Revision 1.69 2007/04/09 16:31:06 ncq 1604 # - add _on_contribute 1605 # 1606 # Revision 1.68 2007/04/08 21:17:14 ncq 1607 # - add more event handlers to data mining panel 1608 # 1609 # Revision 1.67 2007/04/07 22:45:28 ncq 1610 # - add save handler to data mining panel 1611 # 1612 # Revision 1.66 2007/04/06 23:15:21 ncq 1613 # - add data mining panel 1614 # 1615 # Revision 1.65 2007/04/01 15:29:51 ncq 1616 # - safely get_encoding() 1617 # 1618 # Revision 1.64 2007/03/02 15:38:47 ncq 1619 # - decode() strftime() to u'' 1620 # 1621 # Revision 1.63 2007/02/22 17:41:13 ncq 1622 # - adjust to gmPerson changes 1623 # 1624 # Revision 1.62 2007/02/17 14:01:26 ncq 1625 # - gmCurrentProvider.workplace now property 1626 # - notify about birthday after activating patient 1627 # - remove crufty code/docs 1628 # 1629 # Revision 1.61 2007/02/15 14:58:08 ncq 1630 # - tie KVKs intoi external patient sources framework 1631 # 1632 # Revision 1.60 2007/02/13 17:07:38 ncq 1633 # - tie PracSoft PATIENTS.IN file into external patients framework 1634 # - *always* let user decide on whether to activate an external patient 1635 # even if only a single source provides a patient 1636 # 1637 # Revision 1.59 2007/01/20 22:52:27 ncq 1638 # - .KeyCode -> GetKeyCode() 1639 # 1640 # Revision 1.58 2007/01/18 22:07:52 ncq 1641 # - (Get)KeyCode() -> KeyCode so 2.8 can do 1642 # 1643 # Revision 1.57 2007/01/10 23:04:12 ncq 1644 # - support explicit DOB format for xDT files 1645 # 1646 # Revision 1.56 2006/12/13 14:57:16 ncq 1647 # - inform about no patients found in external sources 1648 # 1649 # Revision 1.55 2006/11/24 14:23:19 ncq 1650 # - self.Close() does not need wx.ID_* 1651 # 1652 # Revision 1.54 2006/11/24 09:56:03 ncq 1653 # - improved message when error searching patient 1654 # 1655 # Revision 1.53 2006/11/20 19:11:04 ncq 1656 # - improved message when no matching patient found 1657 # 1658 # Revision 1.52 2006/11/20 17:05:55 ncq 1659 # - do not search if supposed search term matches 'description' of current patient 1660 # 1661 # Revision 1.51 2006/11/01 12:54:40 ncq 1662 # - there may not be a previous encounter so don't try to 1663 # format it's start date if so 1664 # 1665 # Revision 1.50 2006/10/31 12:43:09 ncq 1666 # - out with the crap 1667 # - no more patient expanders 1668 # 1669 # Revision 1.49 2006/10/30 16:46:52 ncq 1670 # - missing encoding in xDT source defs does not *have* to be 1671 # an error as the file itself may contain the encoding itself 1672 # 1673 # Revision 1.48 2006/10/28 14:57:17 ncq 1674 # - use cPatient.get_last_encounter() 1675 # 1676 # Revision 1.47 2006/10/28 12:34:53 ncq 1677 # - make person and dto selector dialogs handle functionality themselves 1678 # - remove person selector panel class 1679 # - act on ENTER/double-click in person/dto select list 1680 # 1681 # Revision 1.46 2006/10/25 07:46:44 ncq 1682 # - Format() -> strftime() since datetime.datetime does not have .Format() 1683 # 1684 # Revision 1.45 2006/10/24 13:26:43 ncq 1685 # - switch to gmPG2 1686 # 1687 # Revision 1.44 2006/09/13 07:55:11 ncq 1688 # - handle encoding in xDT patient sources 1689 # 1690 # Revision 1.43 2006/09/06 07:22:34 ncq 1691 # - add missing import for glob module 1692 # 1693 # Revision 1.42 2006/09/01 14:46:30 ncq 1694 # - add (untested) MCS/Isynet external patient source 1695 # 1696 # Revision 1.41 2006/08/09 15:00:47 ncq 1697 # - better search widget tooltip 1698 # 1699 # Revision 1.40 2006/07/30 18:48:18 ncq 1700 # - invoke load_external_patient on <F2> in searcher 1701 # - robustify by commenting out shaky KVK code 1702 # 1703 # Revision 1.39 2006/07/30 17:51:00 ncq 1704 # - cleanup 1705 # 1706 # Revision 1.38 2006/07/27 17:07:18 ncq 1707 # - cleanup 1708 # - make Cursor-Down the way to invoke previous patients 1709 # 1710 # Revision 1.37 2006/07/26 13:22:37 ncq 1711 # - degrade non-fatal error messages to info messages 1712 # 1713 # Revision 1.36 2006/07/26 13:15:03 ncq 1714 # - cleanup 1715 # 1716 # Revision 1.35 2006/07/24 19:38:39 ncq 1717 # - fix "prev patients" list (alt-p) in patient selector 1718 # - start obsoleting old (ugly) patient pick list 1719 # 1720 # Revision 1.34 2006/07/24 14:18:31 ncq 1721 # - finish pat/dto selection dialogs 1722 # - use them in loading external patients and selecting among matches in search control 1723 # 1724 # Revision 1.33 2006/07/24 11:31:11 ncq 1725 # - cleanup 1726 # - add dialogs to select person/person-dto from list 1727 # - use dto-selection dialog when loading external patient 1728 # 1729 # Revision 1.32 2006/07/22 15:18:24 ncq 1730 # - better error logging 1731 # 1732 # Revision 1.31 2006/07/21 14:48:39 ncq 1733 # - proper returns from load_patient_from_external_sources() 1734 # 1735 # Revision 1.30 2006/07/19 21:41:13 ncq 1736 # - support list of xdt files 1737 # 1738 # Revision 1.29 2006/07/18 21:18:13 ncq 1739 # - add proper load_patient_from_external_sources() 1740 # 1741 # Revision 1.28 2006/05/15 13:36:00 ncq 1742 # - signal cleanup: 1743 # - activating_patient -> pre_patient_selection 1744 # - patient_selected -> post_patient_selection 1745 # 1746 # Revision 1.27 2006/05/12 12:18:11 ncq 1747 # - whoami -> whereami cleanup 1748 # - use gmCurrentProvider() 1749 # 1750 # Revision 1.26 2006/05/04 09:49:20 ncq 1751 # - get_clinical_record() -> get_emr() 1752 # - adjust to changes in set_active_patient() 1753 # - need explicit set_active_patient() after ask_for_patient() if wanted 1754 # 1755 # Revision 1.25 2005/12/14 17:01:51 ncq 1756 # - use improved db cfg option getting 1757 # 1758 # Revision 1.24 2005/09/28 21:27:30 ncq 1759 # - a lot of wx2.6-ification 1760 # 1761 # Revision 1.23 2005/09/27 20:44:59 ncq 1762 # - wx.wx* -> wx.* 1763 # 1764 # Revision 1.22 2005/09/26 18:01:51 ncq 1765 # - use proper way to import wx26 vs wx2.4 1766 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES 1767 # - time for fixup 1768 # 1769 # Revision 1.21 2005/09/24 09:17:29 ncq 1770 # - some wx2.6 compatibility fixes 1771 # 1772 # Revision 1.20 2005/09/12 15:18:05 ncq 1773 # - fix faulty call to SetActivePatient() found by Richard when using 1774 # always_dismiss_after_search 1775 # 1776 # Revision 1.19 2005/09/11 17:35:05 ncq 1777 # - support "patient_search.always_reload_new_patient" 1778 # 1779 # Revision 1.18 2005/09/04 07:31:14 ncq 1780 # - Richard requested the "no active patient" tag be removed 1781 # when no patient is active 1782 # 1783 # Revision 1.17 2005/05/05 06:29:22 ncq 1784 # - if patient not found invoke new patient wizard with activate=true 1785 # 1786 # Revision 1.16 2005/03/08 16:54:13 ncq 1787 # - teach patient picklist about cIdentity 1788 # 1789 # Revision 1.15 2005/02/20 10:33:26 sjtan 1790 # 1791 # disable lose focus to prevent core dumping in a wxPython version. 1792 # 1793 # Revision 1.14 2005/02/13 15:28:07 ncq 1794 # - v_basic_person.i_pk -> pk_identity 1795 # 1796 # Revision 1.13 2005/02/12 13:59:11 ncq 1797 # - v_basic_person.i_id -> i_pk 1798 # 1799 # Revision 1.12 2005/02/01 10:16:07 ihaywood 1800 # refactoring of gmDemographicRecord and follow-on changes as discussed. 1801 # 1802 # gmTopPanel moves to gmHorstSpace 1803 # gmRichardSpace added -- example code at present, haven't even run it myself 1804 # (waiting on some icon .pngs from Richard) 1805 # 1806 # Revision 1.11 2005/01/31 10:37:26 ncq 1807 # - gmPatient.py -> gmPerson.py 1808 # 1809 # Revision 1.10 2004/10/20 12:40:55 ncq 1810 # - some cleanup 1811 # 1812 # Revision 1.9 2004/10/20 07:49:45 sjtan 1813 # small forward wxWidget compatibility change. 1814 # 1815 # Revision 1.7 2004/09/06 22:22:15 ncq 1816 # - properly use setDBParam() 1817 # 1818 # Revision 1.6 2004/09/02 00:40:13 ncq 1819 # - store option always_dismiss_previous_patient if not found 1820 # 1821 # Revision 1.5 2004/09/01 22:04:03 ncq 1822 # - cleanup 1823 # - code order change to avoid exception due to None-check after logging 1824 # 1825 # Revision 1.4 2004/08/29 23:15:58 ncq 1826 # - Richard improved the patient picklist popup 1827 # - plus cleanup/fixes etc 1828 # 1829 # Revision 1.3 2004/08/24 15:41:13 ncq 1830 # - eventually force patient pick list to stay on top 1831 # as suggested by Robin Dunn 1832 # 1833 # Revision 1.2 2004/08/20 13:31:05 ncq 1834 # - cleanup/improve comments/improve naming 1835 # - dismiss patient regardless of search result if so configured 1836 # - don't search on empty search term 1837 # 1838 # Revision 1.1 2004/08/20 06:46:38 ncq 1839 # - used to be gmPatientSelector.py 1840 # 1841 # Revision 1.45 2004/08/19 13:59:14 ncq 1842 # - streamline/cleanup 1843 # - Busy Cursor according to Richard 1844 # 1845 # Revision 1.44 2004/08/18 08:18:35 ncq 1846 # - later wxWidgets version don't support parent=NULL anymore 1847 # 1848 # Revision 1.43 2004/08/02 18:53:36 ncq 1849 # - used wx.Begin/EndBusyCursor() around setting the active patient 1850 # 1851 # Revision 1.42 2004/07/18 19:51:12 ncq 1852 # - cleanup, use True/False, not true/false 1853 # - use run_ro_query(), not run_query() 1854 # 1855 # Revision 1.41 2004/07/15 20:36:11 ncq 1856 # - better default size 1857 # 1858 # Revision 1.40 2004/06/20 16:01:05 ncq 1859 # - please epydoc more carefully 1860 # 1861 # Revision 1.39 2004/06/20 06:49:21 ihaywood 1862 # changes required due to Epydoc's OCD 1863 # 1864 # Revision 1.38 2004/06/04 16:27:12 shilbert 1865 # - giving focus highlights the text and lets you replace it 1866 # 1867 # Revision 1.37 2004/03/27 18:24:11 ncq 1868 # - Ian and I fixed the same bugs again :) 1869 # 1870 # Revision 1.36 2004/03/27 04:37:01 ihaywood 1871 # lnk_person2address now lnk_person_org_address 1872 # sundry bugfixes 1873 # 1874 # Revision 1.35 2004/03/25 11:03:23 ncq 1875 # - getActiveName -> get_names 1876 # 1877 # Revision 1.34 2004/03/20 19:48:07 ncq 1878 # - adapt to flat id list from get_patient_ids 1879 # 1880 # Revision 1.33 2004/03/12 13:23:41 ncq 1881 # - cleanup 1882 # 1883 # Revision 1.32 2004/03/05 11:22:35 ncq 1884 # - import from Gnumed.<pkg> 1885 # 1886 # Revision 1.31 2004/03/04 19:47:06 ncq 1887 # - switch to package based import: from Gnumed.foo import bar 1888 # 1889 # Revision 1.30 2004/02/25 09:46:22 ncq 1890 # - import from pycommon now, not python-common 1891 # 1892 # Revision 1.29 2004/02/05 18:41:31 ncq 1893 # - make _on_patient_selected() thread-safe 1894 # - move SetActivePatient() logic into gmPatient 1895 # 1896 # Revision 1.28 2004/02/04 00:55:02 ncq 1897 # - moved UI-independant patient searching code into business/gmPatient.py where it belongs 1898 # 1899 # Revision 1.27 2003/11/22 14:49:32 ncq 1900 # - fix typo 1901 # 1902 # Revision 1.26 2003/11/22 00:26:10 ihaywood 1903 # Set coding to latin-1 to please python 2.3 1904 # 1905 # Revision 1.25 2003/11/18 23:34:02 ncq 1906 # - don't use reload to force reload of same patient 1907 # 1908 # Revision 1.24 2003/11/17 10:56:38 sjtan 1909 # 1910 # synced and commiting. 1911 # 1912 # Revision 1.23 2003/11/09 17:29:22 shilbert 1913 # - ['demographics'] -> ['demographic record'] 1914 # 1915 # Revision 1.22 2003/11/07 20:44:11 ncq 1916 # - some cleanup 1917 # - listen to patient_selected by other widgets 1918 # 1919 # Revision 1.21 2003/11/04 00:22:46 ncq 1920 # - remove unneeded import 1921 # 1922 # Revision 1.20 2003/10/26 17:42:51 ncq 1923 # - cleanup 1924 # 1925 # Revision 1.19 2003/10/26 11:27:10 ihaywood 1926 # gmPatient is now the "patient stub", all demographics stuff in gmDemographics. 1927 # 1928 # Ergregious breakages are fixed, but needs more work 1929 # 1930 # Revision 1.18 2003/10/26 01:36:13 ncq 1931 # - gmTmpPatient -> gmPatient 1932 # 1933 # Revision 1.17 2003/10/19 12:17:57 ncq 1934 # - typo fix 1935 # 1936 # Revision 1.16 2003/09/21 07:52:57 ihaywood 1937 # those bloody umlauts killed by python interpreter! 1938 # 1939 # Revision 1.15 2003/07/07 08:34:31 ihaywood 1940 # bugfixes on gmdrugs.sql for postgres 7.3 1941 # 1942 # Revision 1.14 2003/07/03 15:22:19 ncq 1943 # - removed unused stuff 1944 # 1945 # Revision 1.13 2003/06/29 14:08:02 ncq 1946 # - extra ; removed 1947 # - kvk/incoming/ as default KVK dir 1948 # 1949 # Revision 1.12 2003/04/09 16:20:19 ncq 1950 # - added set selection on get focus -- but we don't tab in yet !! 1951 # - can now set title on pick list 1952 # - added KVK handling :-) 1953 # 1954 # Revision 1.11 2003/04/04 23:54:30 ncq 1955 # - tweaked some parent and style settings here and there, but still 1956 # not where we want to be with the pick list ... 1957 # 1958 # Revision 1.10 2003/04/04 20:46:45 ncq 1959 # - adapt to new gmCurrentPatient() 1960 # - add (ugly) tooltip 1961 # - break out helper _display_name() 1962 # - fix KeyError on ids[0] 1963 # 1964 # Revision 1.9 2003/04/01 16:01:06 ncq 1965 # - fixed handling of no-patients-found result 1966 # 1967 # Revision 1.8 2003/04/01 15:33:22 ncq 1968 # - and double :: of course, duh 1969 # 1970 # Revision 1.7 2003/04/01 15:32:52 ncq 1971 # - stupid indentation error 1972 # 1973 # Revision 1.6 2003/04/01 12:28:14 ncq 1974 # - factored out _normalize_soundalikes() 1975 # 1976 # Revision 1.5 2003/04/01 09:08:27 ncq 1977 # - better Umlaut replacement 1978 # - safer cursor.close() handling 1979 # 1980 # Revision 1.4 2003/03/31 23:38:16 ncq 1981 # - sensitize() helper for smart names upcasing 1982 # - massively rework queries for speedup 1983 # 1984 # Revision 1.3 2003/03/30 00:24:00 ncq 1985 # - typos 1986 # - (hopefully) less confusing printk()s at startup 1987 # 1988 # Revision 1.2 2003/03/28 15:56:04 ncq 1989 # - adapted to GnuMed CVS structure 1990 # 1991