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

Source Code for Module Gnumed.wxpython.gmPatSearchWidgets

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