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