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: /home/ncq/Projekte/cvs2git/vcs-mirror/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 success = gmDemographicsWidgets.create_new_person(activate = True) 801 if success: 802 self.person = gmPerson.gmCurrentPatient() 803 else: 804 self.person = None 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'], 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.132 2010-02-07 15:17:06 ncq 1394 # - don't use the old new-patient wizard anymore 1395 # 1396 # Revision 1.131 2010/01/31 18:19:41 ncq 1397 # - show hint when no patient selected 1398 # 1399 # Revision 1.130 2009/12/21 15:12:29 ncq 1400 # - cleanup 1401 # - fix typo 1402 # - missing return 1403 # 1404 # Revision 1.129 2009/11/15 01:10:34 ncq 1405 # - cleanup 1406 # 1407 # Revision 1.128 2009/07/17 09:25:06 ncq 1408 # - ! -> Urgency as per list 1409 # - adding acts on the current patient *only* 1410 # - add missing Destroy 1411 # 1412 # Revision 1.127 2009/07/02 20:56:26 ncq 1413 # - used edit area dlg2 1414 # 1415 # Revision 1.126 2009/07/01 17:10:35 ncq 1416 # - need to return state from set_active_patient 1417 # 1418 # Revision 1.125 2009/06/20 12:47:17 ncq 1419 # - only display last encounter in search results if 1420 # patient has clinical data (that is, is a patient) 1421 # 1422 # Revision 1.124 2009/06/04 16:27:47 ncq 1423 # - add set active patient and use it 1424 # - adjust to dob-less persons 1425 # 1426 # Revision 1.123 2009/04/21 17:00:00 ncq 1427 # - edit area dlg now takes single_entry argument 1428 # 1429 # Revision 1.122 2009/02/05 14:30:36 ncq 1430 # - only run new-patient-wizard if user explicitely said so 1431 # - do not try to set active patient if user cancelled new patient wizard 1432 # 1433 # Revision 1.121 2009/02/04 12:35:18 ncq 1434 # - support editing waiting list entries 1435 # 1436 # Revision 1.120 2009/01/30 12:11:43 ncq 1437 # - waiting list entry edit area 1438 # 1439 # Revision 1.119 2009/01/22 11:16:41 ncq 1440 # - implement moving waiting list entries 1441 # 1442 # Revision 1.118 2009/01/21 22:39:02 ncq 1443 # - waiting zones phrasewheel and use it 1444 # 1445 # Revision 1.117 2009/01/21 18:04:41 ncq 1446 # - implement most of waiting list 1447 # 1448 # Revision 1.116 2009/01/17 23:08:31 ncq 1449 # - waiting list 1450 # 1451 # Revision 1.115 2008/12/17 21:59:22 ncq 1452 # - add support for merging patients 1453 # 1454 # Revision 1.114 2008/12/09 23:43:27 ncq 1455 # - use description_gender 1456 # - no more hardcoded plugin raising after patient activation 1457 # 1458 # Revision 1.113 2008/10/12 16:26:46 ncq 1459 # - cleanup 1460 # 1461 # Revision 1.112 2008/09/01 20:28:51 ncq 1462 # - properly handle case when several option sources define AU PracSoft source 1463 # 1464 # Revision 1.111 2008/08/28 18:34:18 ncq 1465 # - make active patient selector react to patient activation, 1466 # name/identity change all by itself with updating its display, 1467 # don't let top panel do it for us 1468 # 1469 # Revision 1.110 2008/07/28 20:27:20 ncq 1470 # - do not try to activate None person 1471 # 1472 # Revision 1.109 2008/07/07 13:43:17 ncq 1473 # - current patient .connected 1474 # 1475 # Revision 1.108 2008/05/13 14:13:57 ncq 1476 # - fix on-focus-select-all behaviour 1477 # - don't display search term after name - when a search failed this gets confusing 1478 # 1479 # Revision 1.107 2008/04/16 20:39:39 ncq 1480 # - working versions of the wxGlade code and use it, too 1481 # - show client version in login dialog 1482 # 1483 # Revision 1.106 2008/03/20 15:31:59 ncq 1484 # - missing \n added 1485 # 1486 # Revision 1.105 2008/03/09 20:18:22 ncq 1487 # - cleanup 1488 # - load_patient_* -> get_person_* 1489 # - make cPatientSelector() generic -> cPersonSearchCtrl() 1490 # 1491 # Revision 1.104 2008/02/25 17:40:18 ncq 1492 # - new style logging 1493 # 1494 # Revision 1.103 2008/01/30 14:09:39 ncq 1495 # - switch to new style cfg file support 1496 # - cleanup 1497 # 1498 # Revision 1.102 2008/01/27 21:17:49 ncq 1499 # - improve message on patient not found 1500 # 1501 # Revision 1.101 2008/01/22 12:24:55 ncq 1502 # - include search fragment into patient name display 1503 # - reenable on kill focus handler restoring patient name 1504 # - improved wording on patient not found 1505 # 1506 # Revision 1.100 2008/01/11 16:15:33 ncq 1507 # - first/last -> first-/lastnames 1508 # 1509 # Revision 1.99 2008/01/05 16:41:27 ncq 1510 # - remove logging from gm_show_*() 1511 # 1512 # Revision 1.98 2007/12/11 12:49:26 ncq 1513 # - explicit signal handling 1514 # 1515 # Revision 1.97 2007/11/12 23:05:55 ncq 1516 # - import extra data from DTOs 1517 # 1518 # Revision 1.96 2007/11/10 20:58:59 ncq 1519 # - use dto.get_candidate_identities() and dto.delete_from_source() 1520 # 1521 # Revision 1.95 2007/10/19 12:52:34 ncq 1522 # - implement search_immediately in load_patient_from_external_source() 1523 # 1524 # Revision 1.94 2007/10/12 14:20:09 ncq 1525 # - prepare "activate_immediately" in load_patient_from_external_sources() 1526 # 1527 # Revision 1.93 2007/10/12 13:33:06 ncq 1528 # - if only one external patient available - activate it right away 1529 # 1530 # Revision 1.92 2007/10/11 12:15:09 ncq 1531 # - make filling patient selector list more robust in absence of match_type field 1532 # 1533 # Revision 1.91 2007/10/07 12:32:42 ncq 1534 # - workplace property now on gmSurgery.gmCurrentPractice() borg 1535 # 1536 # Revision 1.90 2007/09/10 12:38:12 ncq 1537 # - improve wording on announcing upcoming patient birthday 1538 # 1539 # Revision 1.89 2007/08/28 14:18:13 ncq 1540 # - no more gm_statustext() 1541 # 1542 # Revision 1.88 2007/08/12 00:12:41 ncq 1543 # - no more gmSignals.py 1544 # 1545 # Revision 1.87 2007/07/17 16:00:28 ncq 1546 # - check existence of PracSoft import file 1547 # 1548 # Revision 1.86 2007/07/11 21:11:08 ncq 1549 # - display patient locked state 1550 # - listen on patient lock/unlock events 1551 # 1552 # Revision 1.85 2007/07/09 12:46:33 ncq 1553 # - move cDataMiningPnl to gmDataMiningWidgets.py 1554 # 1555 # Revision 1.84 2007/07/07 12:43:25 ncq 1556 # - in cDataMiningPnl use cPatientListingCtrl 1557 # 1558 # Revision 1.83 2007/06/28 12:40:48 ncq 1559 # - handle dto.dob being optional now 1560 # - support dto source gotten from xdt file 1561 # 1562 # Revision 1.82 2007/06/12 16:03:58 ncq 1563 # - some comments 1564 # - fix typo 1565 # - better error display on failing queries 1566 # 1567 # Revision 1.81 2007/06/10 10:12:55 ncq 1568 # - options need names 1569 # 1570 # Revision 1.80 2007/05/18 15:55:58 ncq 1571 # - auto-select first item in person/dto selector 1572 # 1573 # Revision 1.79 2007/05/14 14:56:41 ncq 1574 # - fix typo 1575 # 1576 # Revision 1.78 2007/05/14 13:52:24 ncq 1577 # - add display_name() in two places to fix visual glitch with search 1578 # 1579 # Revision 1.77 2007/05/14 13:37:42 ncq 1580 # - don't do anything if the only external patient is 1581 # already the active patient in GNUmed 1582 # 1583 # Revision 1.76 2007/05/14 13:11:24 ncq 1584 # - use statustext() signal 1585 # 1586 # Revision 1.75 2007/05/07 08:04:36 ncq 1587 # - a bit of cleanup 1588 # 1589 # Revision 1.74 2007/04/19 13:13:47 ncq 1590 # - cleanup 1591 # 1592 # Revision 1.73 2007/04/11 14:53:33 ncq 1593 # - do some safeguarding against binary/large files being dropped onto 1594 # the data mining plugin - check mimetype and size 1595 # 1596 # Revision 1.72 2007/04/09 22:03:57 ncq 1597 # - make data mining panel a file drop target 1598 # 1599 # Revision 1.71 2007/04/09 21:12:49 ncq 1600 # - better wording in contribute email 1601 # - properly unicode() SQL results 1602 # 1603 # Revision 1.70 2007/04/09 18:52:47 ncq 1604 # - magic patient activation from report result list 1605 # 1606 # Revision 1.69 2007/04/09 16:31:06 ncq 1607 # - add _on_contribute 1608 # 1609 # Revision 1.68 2007/04/08 21:17:14 ncq 1610 # - add more event handlers to data mining panel 1611 # 1612 # Revision 1.67 2007/04/07 22:45:28 ncq 1613 # - add save handler to data mining panel 1614 # 1615 # Revision 1.66 2007/04/06 23:15:21 ncq 1616 # - add data mining panel 1617 # 1618 # Revision 1.65 2007/04/01 15:29:51 ncq 1619 # - safely get_encoding() 1620 # 1621 # Revision 1.64 2007/03/02 15:38:47 ncq 1622 # - decode() strftime() to u'' 1623 # 1624 # Revision 1.63 2007/02/22 17:41:13 ncq 1625 # - adjust to gmPerson changes 1626 # 1627 # Revision 1.62 2007/02/17 14:01:26 ncq 1628 # - gmCurrentProvider.workplace now property 1629 # - notify about birthday after activating patient 1630 # - remove crufty code/docs 1631 # 1632 # Revision 1.61 2007/02/15 14:58:08 ncq 1633 # - tie KVKs intoi external patient sources framework 1634 # 1635 # Revision 1.60 2007/02/13 17:07:38 ncq 1636 # - tie PracSoft PATIENTS.IN file into external patients framework 1637 # - *always* let user decide on whether to activate an external patient 1638 # even if only a single source provides a patient 1639 # 1640 # Revision 1.59 2007/01/20 22:52:27 ncq 1641 # - .KeyCode -> GetKeyCode() 1642 # 1643 # Revision 1.58 2007/01/18 22:07:52 ncq 1644 # - (Get)KeyCode() -> KeyCode so 2.8 can do 1645 # 1646 # Revision 1.57 2007/01/10 23:04:12 ncq 1647 # - support explicit DOB format for xDT files 1648 # 1649 # Revision 1.56 2006/12/13 14:57:16 ncq 1650 # - inform about no patients found in external sources 1651 # 1652 # Revision 1.55 2006/11/24 14:23:19 ncq 1653 # - self.Close() does not need wx.ID_* 1654 # 1655 # Revision 1.54 2006/11/24 09:56:03 ncq 1656 # - improved message when error searching patient 1657 # 1658 # Revision 1.53 2006/11/20 19:11:04 ncq 1659 # - improved message when no matching patient found 1660 # 1661 # Revision 1.52 2006/11/20 17:05:55 ncq 1662 # - do not search if supposed search term matches 'description' of current patient 1663 # 1664 # Revision 1.51 2006/11/01 12:54:40 ncq 1665 # - there may not be a previous encounter so don't try to 1666 # format it's start date if so 1667 # 1668 # Revision 1.50 2006/10/31 12:43:09 ncq 1669 # - out with the crap 1670 # - no more patient expanders 1671 # 1672 # Revision 1.49 2006/10/30 16:46:52 ncq 1673 # - missing encoding in xDT source defs does not *have* to be 1674 # an error as the file itself may contain the encoding itself 1675 # 1676 # Revision 1.48 2006/10/28 14:57:17 ncq 1677 # - use cPatient.get_last_encounter() 1678 # 1679 # Revision 1.47 2006/10/28 12:34:53 ncq 1680 # - make person and dto selector dialogs handle functionality themselves 1681 # - remove person selector panel class 1682 # - act on ENTER/double-click in person/dto select list 1683 # 1684 # Revision 1.46 2006/10/25 07:46:44 ncq 1685 # - Format() -> strftime() since datetime.datetime does not have .Format() 1686 # 1687 # Revision 1.45 2006/10/24 13:26:43 ncq 1688 # - switch to gmPG2 1689 # 1690 # Revision 1.44 2006/09/13 07:55:11 ncq 1691 # - handle encoding in xDT patient sources 1692 # 1693 # Revision 1.43 2006/09/06 07:22:34 ncq 1694 # - add missing import for glob module 1695 # 1696 # Revision 1.42 2006/09/01 14:46:30 ncq 1697 # - add (untested) MCS/Isynet external patient source 1698 # 1699 # Revision 1.41 2006/08/09 15:00:47 ncq 1700 # - better search widget tooltip 1701 # 1702 # Revision 1.40 2006/07/30 18:48:18 ncq 1703 # - invoke load_external_patient on <F2> in searcher 1704 # - robustify by commenting out shaky KVK code 1705 # 1706 # Revision 1.39 2006/07/30 17:51:00 ncq 1707 # - cleanup 1708 # 1709 # Revision 1.38 2006/07/27 17:07:18 ncq 1710 # - cleanup 1711 # - make Cursor-Down the way to invoke previous patients 1712 # 1713 # Revision 1.37 2006/07/26 13:22:37 ncq 1714 # - degrade non-fatal error messages to info messages 1715 # 1716 # Revision 1.36 2006/07/26 13:15:03 ncq 1717 # - cleanup 1718 # 1719 # Revision 1.35 2006/07/24 19:38:39 ncq 1720 # - fix "prev patients" list (alt-p) in patient selector 1721 # - start obsoleting old (ugly) patient pick list 1722 # 1723 # Revision 1.34 2006/07/24 14:18:31 ncq 1724 # - finish pat/dto selection dialogs 1725 # - use them in loading external patients and selecting among matches in search control 1726 # 1727 # Revision 1.33 2006/07/24 11:31:11 ncq 1728 # - cleanup 1729 # - add dialogs to select person/person-dto from list 1730 # - use dto-selection dialog when loading external patient 1731 # 1732 # Revision 1.32 2006/07/22 15:18:24 ncq 1733 # - better error logging 1734 # 1735 # Revision 1.31 2006/07/21 14:48:39 ncq 1736 # - proper returns from load_patient_from_external_sources() 1737 # 1738 # Revision 1.30 2006/07/19 21:41:13 ncq 1739 # - support list of xdt files 1740 # 1741 # Revision 1.29 2006/07/18 21:18:13 ncq 1742 # - add proper load_patient_from_external_sources() 1743 # 1744 # Revision 1.28 2006/05/15 13:36:00 ncq 1745 # - signal cleanup: 1746 # - activating_patient -> pre_patient_selection 1747 # - patient_selected -> post_patient_selection 1748 # 1749 # Revision 1.27 2006/05/12 12:18:11 ncq 1750 # - whoami -> whereami cleanup 1751 # - use gmCurrentProvider() 1752 # 1753 # Revision 1.26 2006/05/04 09:49:20 ncq 1754 # - get_clinical_record() -> get_emr() 1755 # - adjust to changes in set_active_patient() 1756 # - need explicit set_active_patient() after ask_for_patient() if wanted 1757 # 1758 # Revision 1.25 2005/12/14 17:01:51 ncq 1759 # - use improved db cfg option getting 1760 # 1761 # Revision 1.24 2005/09/28 21:27:30 ncq 1762 # - a lot of wx2.6-ification 1763 # 1764 # Revision 1.23 2005/09/27 20:44:59 ncq 1765 # - wx.wx* -> wx.* 1766 # 1767 # Revision 1.22 2005/09/26 18:01:51 ncq 1768 # - use proper way to import wx26 vs wx2.4 1769 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES 1770 # - time for fixup 1771 # 1772 # Revision 1.21 2005/09/24 09:17:29 ncq 1773 # - some wx2.6 compatibility fixes 1774 # 1775 # Revision 1.20 2005/09/12 15:18:05 ncq 1776 # - fix faulty call to SetActivePatient() found by Richard when using 1777 # always_dismiss_after_search 1778 # 1779 # Revision 1.19 2005/09/11 17:35:05 ncq 1780 # - support "patient_search.always_reload_new_patient" 1781 # 1782 # Revision 1.18 2005/09/04 07:31:14 ncq 1783 # - Richard requested the "no active patient" tag be removed 1784 # when no patient is active 1785 # 1786 # Revision 1.17 2005/05/05 06:29:22 ncq 1787 # - if patient not found invoke new patient wizard with activate=true 1788 # 1789 # Revision 1.16 2005/03/08 16:54:13 ncq 1790 # - teach patient picklist about cIdentity 1791 # 1792 # Revision 1.15 2005/02/20 10:33:26 sjtan 1793 # 1794 # disable lose focus to prevent core dumping in a wxPython version. 1795 # 1796 # Revision 1.14 2005/02/13 15:28:07 ncq 1797 # - v_basic_person.i_pk -> pk_identity 1798 # 1799 # Revision 1.13 2005/02/12 13:59:11 ncq 1800 # - v_basic_person.i_id -> i_pk 1801 # 1802 # Revision 1.12 2005/02/01 10:16:07 ihaywood 1803 # refactoring of gmDemographicRecord and follow-on changes as discussed. 1804 # 1805 # gmTopPanel moves to gmHorstSpace 1806 # gmRichardSpace added -- example code at present, haven't even run it myself 1807 # (waiting on some icon .pngs from Richard) 1808 # 1809 # Revision 1.11 2005/01/31 10:37:26 ncq 1810 # - gmPatient.py -> gmPerson.py 1811 # 1812 # Revision 1.10 2004/10/20 12:40:55 ncq 1813 # - some cleanup 1814 # 1815 # Revision 1.9 2004/10/20 07:49:45 sjtan 1816 # small forward wxWidget compatibility change. 1817 # 1818 # Revision 1.7 2004/09/06 22:22:15 ncq 1819 # - properly use setDBParam() 1820 # 1821 # Revision 1.6 2004/09/02 00:40:13 ncq 1822 # - store option always_dismiss_previous_patient if not found 1823 # 1824 # Revision 1.5 2004/09/01 22:04:03 ncq 1825 # - cleanup 1826 # - code order change to avoid exception due to None-check after logging 1827 # 1828 # Revision 1.4 2004/08/29 23:15:58 ncq 1829 # - Richard improved the patient picklist popup 1830 # - plus cleanup/fixes etc 1831 # 1832 # Revision 1.3 2004/08/24 15:41:13 ncq 1833 # - eventually force patient pick list to stay on top 1834 # as suggested by Robin Dunn 1835 # 1836 # Revision 1.2 2004/08/20 13:31:05 ncq 1837 # - cleanup/improve comments/improve naming 1838 # - dismiss patient regardless of search result if so configured 1839 # - don't search on empty search term 1840 # 1841 # Revision 1.1 2004/08/20 06:46:38 ncq 1842 # - used to be gmPatientSelector.py 1843 # 1844 # Revision 1.45 2004/08/19 13:59:14 ncq 1845 # - streamline/cleanup 1846 # - Busy Cursor according to Richard 1847 # 1848 # Revision 1.44 2004/08/18 08:18:35 ncq 1849 # - later wxWidgets version don't support parent=NULL anymore 1850 # 1851 # Revision 1.43 2004/08/02 18:53:36 ncq 1852 # - used wx.Begin/EndBusyCursor() around setting the active patient 1853 # 1854 # Revision 1.42 2004/07/18 19:51:12 ncq 1855 # - cleanup, use True/False, not true/false 1856 # - use run_ro_query(), not run_query() 1857 # 1858 # Revision 1.41 2004/07/15 20:36:11 ncq 1859 # - better default size 1860 # 1861 # Revision 1.40 2004/06/20 16:01:05 ncq 1862 # - please epydoc more carefully 1863 # 1864 # Revision 1.39 2004/06/20 06:49:21 ihaywood 1865 # changes required due to Epydoc's OCD 1866 # 1867 # Revision 1.38 2004/06/04 16:27:12 shilbert 1868 # - giving focus highlights the text and lets you replace it 1869 # 1870 # Revision 1.37 2004/03/27 18:24:11 ncq 1871 # - Ian and I fixed the same bugs again :) 1872 # 1873 # Revision 1.36 2004/03/27 04:37:01 ihaywood 1874 # lnk_person2address now lnk_person_org_address 1875 # sundry bugfixes 1876 # 1877 # Revision 1.35 2004/03/25 11:03:23 ncq 1878 # - getActiveName -> get_names 1879 # 1880 # Revision 1.34 2004/03/20 19:48:07 ncq 1881 # - adapt to flat id list from get_patient_ids 1882 # 1883 # Revision 1.33 2004/03/12 13:23:41 ncq 1884 # - cleanup 1885 # 1886 # Revision 1.32 2004/03/05 11:22:35 ncq 1887 # - import from Gnumed.<pkg> 1888 # 1889 # Revision 1.31 2004/03/04 19:47:06 ncq 1890 # - switch to package based import: from Gnumed.foo import bar 1891 # 1892 # Revision 1.30 2004/02/25 09:46:22 ncq 1893 # - import from pycommon now, not python-common 1894 # 1895 # Revision 1.29 2004/02/05 18:41:31 ncq 1896 # - make _on_patient_selected() thread-safe 1897 # - move SetActivePatient() logic into gmPatient 1898 # 1899 # Revision 1.28 2004/02/04 00:55:02 ncq 1900 # - moved UI-independant patient searching code into business/gmPatient.py where it belongs 1901 # 1902 # Revision 1.27 2003/11/22 14:49:32 ncq 1903 # - fix typo 1904 # 1905 # Revision 1.26 2003/11/22 00:26:10 ihaywood 1906 # Set coding to latin-1 to please python 2.3 1907 # 1908 # Revision 1.25 2003/11/18 23:34:02 ncq 1909 # - don't use reload to force reload of same patient 1910 # 1911 # Revision 1.24 2003/11/17 10:56:38 sjtan 1912 # 1913 # synced and commiting. 1914 # 1915 # Revision 1.23 2003/11/09 17:29:22 shilbert 1916 # - ['demographics'] -> ['demographic record'] 1917 # 1918 # Revision 1.22 2003/11/07 20:44:11 ncq 1919 # - some cleanup 1920 # - listen to patient_selected by other widgets 1921 # 1922 # Revision 1.21 2003/11/04 00:22:46 ncq 1923 # - remove unneeded import 1924 # 1925 # Revision 1.20 2003/10/26 17:42:51 ncq 1926 # - cleanup 1927 # 1928 # Revision 1.19 2003/10/26 11:27:10 ihaywood 1929 # gmPatient is now the "patient stub", all demographics stuff in gmDemographics. 1930 # 1931 # Ergregious breakages are fixed, but needs more work 1932 # 1933 # Revision 1.18 2003/10/26 01:36:13 ncq 1934 # - gmTmpPatient -> gmPatient 1935 # 1936 # Revision 1.17 2003/10/19 12:17:57 ncq 1937 # - typo fix 1938 # 1939 # Revision 1.16 2003/09/21 07:52:57 ihaywood 1940 # those bloody umlauts killed by python interpreter! 1941 # 1942 # Revision 1.15 2003/07/07 08:34:31 ihaywood 1943 # bugfixes on gmdrugs.sql for postgres 7.3 1944 # 1945 # Revision 1.14 2003/07/03 15:22:19 ncq 1946 # - removed unused stuff 1947 # 1948 # Revision 1.13 2003/06/29 14:08:02 ncq 1949 # - extra ; removed 1950 # - kvk/incoming/ as default KVK dir 1951 # 1952 # Revision 1.12 2003/04/09 16:20:19 ncq 1953 # - added set selection on get focus -- but we don't tab in yet !! 1954 # - can now set title on pick list 1955 # - added KVK handling :-) 1956 # 1957 # Revision 1.11 2003/04/04 23:54:30 ncq 1958 # - tweaked some parent and style settings here and there, but still 1959 # not where we want to be with the pick list ... 1960 # 1961 # Revision 1.10 2003/04/04 20:46:45 ncq 1962 # - adapt to new gmCurrentPatient() 1963 # - add (ugly) tooltip 1964 # - break out helper _display_name() 1965 # - fix KeyError on ids[0] 1966 # 1967 # Revision 1.9 2003/04/01 16:01:06 ncq 1968 # - fixed handling of no-patients-found result 1969 # 1970 # Revision 1.8 2003/04/01 15:33:22 ncq 1971 # - and double :: of course, duh 1972 # 1973 # Revision 1.7 2003/04/01 15:32:52 ncq 1974 # - stupid indentation error 1975 # 1976 # Revision 1.6 2003/04/01 12:28:14 ncq 1977 # - factored out _normalize_soundalikes() 1978 # 1979 # Revision 1.5 2003/04/01 09:08:27 ncq 1980 # - better Umlaut replacement 1981 # - safer cursor.close() handling 1982 # 1983 # Revision 1.4 2003/03/31 23:38:16 ncq 1984 # - sensitize() helper for smart names upcasing 1985 # - massively rework queries for speedup 1986 # 1987 # Revision 1.3 2003/03/30 00:24:00 ncq 1988 # - typos 1989 # - (hopefully) less confusing printk()s at startup 1990 # 1991 # Revision 1.2 2003/03/28 15:56:04 ncq 1992 # - adapted to GnuMed CVS structure 1993 # 1994