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

Source Code for Module Gnumed.wxpython.gmEMRBrowser

   1  """GNUmed patient EMR tree browser.""" 
   2  #================================================================ 
   3  __author__ = "cfmoro1976@yahoo.es, sjtan@swiftdsl.com.au, Karsten.Hilbert@gmx.net" 
   4  __license__ = "GPL v2 or later" 
   5   
   6  # std lib 
   7  import sys 
   8  import os.path 
   9  import StringIO 
  10  import codecs 
  11  import logging 
  12   
  13   
  14  # 3rd party 
  15  import wx 
  16   
  17   
  18  # GNUmed libs 
  19  from Gnumed.pycommon import gmI18N 
  20  from Gnumed.pycommon import gmDispatcher 
  21  from Gnumed.pycommon import gmExceptions 
  22  from Gnumed.pycommon import gmTools 
  23  from Gnumed.exporters import gmPatientExporter 
  24  from Gnumed.business import gmEMRStructItems 
  25  from Gnumed.business import gmPerson 
  26  from Gnumed.business import gmSOAPimporter 
  27  from Gnumed.business import gmPersonSearch 
  28  from Gnumed.wxpython import gmGuiHelpers 
  29  from Gnumed.wxpython import gmEMRStructWidgets 
  30  from Gnumed.wxpython import gmSOAPWidgets 
  31  from Gnumed.wxpython import gmAllergyWidgets 
  32  from Gnumed.wxpython import gmDemographicsWidgets 
  33  from Gnumed.wxpython import gmNarrativeWidgets 
  34  from Gnumed.wxpython import gmPatSearchWidgets 
  35  from Gnumed.wxpython import gmVaccWidgets 
  36  from Gnumed.wxpython import gmFamilyHistoryWidgets 
  37   
  38   
  39  _log = logging.getLogger('gm.ui') 
  40   
  41  #============================================================ 
42 -def export_emr_to_ascii(parent=None):
43 """ 44 Dump the patient's EMR from GUI client 45 @param parent - The parent widget 46 @type parent - A wx.Window instance 47 """ 48 # sanity checks 49 if parent is None: 50 raise TypeError('expected wx.Window instance as parent, got <None>') 51 52 pat = gmPerson.gmCurrentPatient() 53 if not pat.connected: 54 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.')) 55 return False 56 57 # get file name 58 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files")) 59 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))) 60 gmTools.mkdir(defdir) 61 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames']) 62 dlg = wx.FileDialog ( 63 parent = parent, 64 message = _("Save patient's EMR as..."), 65 defaultDir = defdir, 66 defaultFile = fname, 67 wildcard = wc, 68 style = wx.SAVE 69 ) 70 choice = dlg.ShowModal() 71 fname = dlg.GetPath() 72 dlg.Destroy() 73 if choice != wx.ID_OK: 74 return None 75 76 _log.debug('exporting EMR to [%s]', fname) 77 78 output_file = codecs.open(fname, 'wb', encoding='utf8', errors='replace') 79 exporter = gmPatientExporter.cEmrExport(patient = pat) 80 exporter.set_output_file(output_file) 81 exporter.dump_constraints() 82 exporter.dump_demographic_record(True) 83 exporter.dump_clinical_record() 84 exporter.dump_med_docs() 85 output_file.close() 86 87 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False) 88 return fname
89 #============================================================
90 -class cEMRTree(wx.TreeCtrl, gmGuiHelpers.cTreeExpansionHistoryMixin):
91 """This wx.TreeCtrl derivative displays a tree view of the medical record.""" 92 93 #--------------------------------------------------------
94 - def __init__(self, parent, id, *args, **kwds):
95 """Set up our specialised tree. 96 """ 97 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE 98 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds) 99 100 gmGuiHelpers.cTreeExpansionHistoryMixin.__init__(self) 101 102 self.__details_display = None 103 self.__details_display_mode = u'details' # "details" or "journal" 104 self.__enable_display_mode_selection = None 105 self.__pat = gmPerson.gmCurrentPatient() 106 self.__curr_node = None 107 #self.__exporter = gmPatientExporter.cEmrExport(patient = self.__pat) 108 109 self._old_cursor_pos = None 110 111 self.__make_popup_menus() 112 self.__register_events()
113 #-------------------------------------------------------- 114 # external API 115 #--------------------------------------------------------
116 - def refresh(self):
117 if not self.__pat.connected: 118 gmDispatcher.send(signal='statustext', msg=_('Cannot load clinical narrative. No active patient.'),) 119 return False 120 121 if not self.__populate_tree(): 122 return False 123 124 return True
125 #--------------------------------------------------------
126 - def set_narrative_display(self, narrative_display=None):
127 self.__details_display = narrative_display
128 #--------------------------------------------------------
129 - def set_image_display(self, image_display=None):
130 self.__img_display = image_display
131 #--------------------------------------------------------
133 if not callable(callback): 134 raise ValueError('callback [%s] not callable' % callback) 135 136 self.__enable_display_mode_selection = callback
137 #-------------------------------------------------------- 138 # internal helpers 139 #--------------------------------------------------------
140 - def __register_events(self):
141 """Configures enabled event signals.""" 142 wx.EVT_TREE_SEL_CHANGED (self, self.GetId(), self._on_tree_item_selected) 143 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self._on_tree_item_right_clicked) 144 self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._on_tree_item_expanding) 145 146 # handle tooltips 147 # wx.EVT_MOTION(self, self._on_mouse_motion) 148 wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip) 149 150 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db) 151 gmDispatcher.connect(signal = 'episode_mod_db', receiver = self._on_episode_mod_db) 152 gmDispatcher.connect(signal = 'health_issue_mod_db', receiver = self._on_issue_mod_db) 153 gmDispatcher.connect(signal = 'family_history_mod_db', receiver = self._on_issue_mod_db)
154 #--------------------------------------------------------
155 - def clear_tree(self):
156 self.DeleteAllItems()
157 #--------------------------------------------------------
158 - def __populate_tree(self):
159 """Updates EMR browser data.""" 160 # FIXME: auto select the previously self.__curr_node if not None 161 # FIXME: error handling 162 163 if not self.__pat.connected: 164 return 165 166 wx.BeginBusyCursor() 167 # self.snapshot_expansion() 168 # init new tree 169 root_item = self.__populate_root_node() 170 # self.__exporter.get_historical_tree(self) # this is slow 171 self.__curr_node = root_item 172 self.SelectItem(root_item) 173 self.Expand(root_item) 174 self.__update_text_for_selected_node() # this is fairly slow, too 175 # self.restore_expansion() 176 wx.EndBusyCursor() 177 return True
178 #--------------------------------------------------------
179 - def __populate_root_node(self):
180 181 self.DeleteAllItems() 182 183 root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name()) 184 self.SetItemPyData(root_item, None) 185 self.SetItemHasChildren(root_item, True) 186 187 self.__root_tooltip = self.__pat['description_gender'] + u'\n' 188 if self.__pat['deceased'] is None: 189 self.__root_tooltip += u' %s (%s)\n\n' % ( 190 self.__pat.get_formatted_dob(format = '%d %b %Y', encoding = gmI18N.get_encoding()), 191 self.__pat['medical_age'] 192 ) 193 else: 194 template = u' %s - %s (%s)\n\n' 195 self.__root_tooltip += template % ( 196 self.__pat.get_formatted_dob(format = '%d.%b %Y', encoding = gmI18N.get_encoding()), 197 self.__pat['deceased'].strftime('%d.%b %Y').decode(gmI18N.get_encoding()), 198 self.__pat['medical_age'] 199 ) 200 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], u'', u'%s\n\n') 201 doc = self.__pat.primary_provider 202 if doc is not None: 203 self.__root_tooltip += u'%s:\n' % _('Primary provider in this praxis') 204 self.__root_tooltip += u' %s %s %s (%s)%s\n\n' % ( 205 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])), 206 doc['firstnames'], 207 doc['lastnames'], 208 doc['short_alias'], 209 gmTools.bool2subst(doc['is_active'], u'', u' [%s]' % _('inactive')) 210 ) 211 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)): 212 self.__root_tooltip += _('In case of emergency contact:') + u'\n' 213 if self.__pat['emergency_contact'] is not None: 214 self.__root_tooltip += gmTools.wrap ( 215 text = u'%s\n' % self.__pat['emergency_contact'], 216 width = 60, 217 initial_indent = u' ', 218 subsequent_indent = u' ' 219 ) 220 if self.__pat['pk_emergency_contact'] is not None: 221 contact = self.__pat.emergency_contact_in_database 222 self.__root_tooltip += u' %s\n' % contact['description_gender'] 223 self.__root_tooltip = self.__root_tooltip.strip('\n') 224 if self.__root_tooltip == u'': 225 self.__root_tooltip = u' ' 226 227 return root_item
228 #--------------------------------------------------------
230 """Displays information for the selected tree node.""" 231 232 if self.__details_display is None: 233 self.__img_display.clear() 234 return 235 236 if self.__curr_node is None: 237 self.__img_display.clear() 238 return 239 240 node_data = self.GetPyData(self.__curr_node) 241 doc_folder = self.__pat.get_document_folder() 242 243 if isinstance(node_data, gmEMRStructItems.cHealthIssue): 244 self.__enable_display_mode_selection(True) 245 if self.__details_display_mode == u'details': 246 txt = node_data.format(left_margin=1, patient = self.__pat) 247 else: 248 txt = node_data.format_as_journal(left_margin = 1) 249 250 self.__img_display.refresh ( 251 document_folder = doc_folder, 252 episodes = [ epi['pk_episode'] for epi in node_data.episodes ] 253 ) 254 255 elif isinstance(node_data, type({})): 256 self.__enable_display_mode_selection(False) 257 # FIXME: turn into real dummy issue 258 txt = _('Pool of unassociated episodes:\n\n "%s"') % node_data['description'] 259 self.__img_display.clear() 260 261 elif isinstance(node_data, gmEMRStructItems.cEpisode): 262 self.__enable_display_mode_selection(True) 263 if self.__details_display_mode == u'details': 264 txt = node_data.format(left_margin = 1, patient = self.__pat) 265 else: 266 txt = node_data.format_as_journal(left_margin = 1) 267 self.__img_display.refresh ( 268 document_folder = doc_folder, 269 episodes = [node_data['pk_episode']] 270 ) 271 272 elif isinstance(node_data, gmEMRStructItems.cEncounter): 273 self.__enable_display_mode_selection(False) 274 epi = self.GetPyData(self.GetItemParent(self.__curr_node)) 275 txt = node_data.format ( 276 episodes = [epi['pk_episode']], 277 with_soap = True, 278 left_margin = 1, 279 patient = self.__pat, 280 with_co_encountlet_hints = True 281 ) 282 self.__img_display.refresh ( 283 document_folder = doc_folder, 284 episodes = [epi['pk_episode']], 285 encounter = node_data['pk_encounter'] 286 ) 287 288 # root node == EMR level 289 else: 290 self.__enable_display_mode_selection(False) 291 emr = self.__pat.get_emr() 292 txt = emr.format_summary(dob = self.__pat['dob']) 293 self.__img_display.clear() 294 295 self.__details_display.Clear() 296 self.__details_display.WriteText(txt) 297 self.__details_display.ShowPosition(0)
298 #--------------------------------------------------------
299 - def __make_popup_menus(self):
300 301 # - episodes 302 self.__epi_context_popup = wx.Menu(title = _('Episode Actions:')) 303 304 menu_id = wx.NewId() 305 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Edit details'))) 306 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__edit_episode) 307 308 menu_id = wx.NewId() 309 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Delete'))) 310 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__delete_episode) 311 312 menu_id = wx.NewId() 313 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Promote'))) 314 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__promote_episode_to_issue) 315 316 menu_id = wx.NewId() 317 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Move encounters'))) 318 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__move_encounters) 319 320 # - encounters 321 self.__enc_context_popup = wx.Menu(title = _('Encounter Actions:')) 322 # - move data 323 menu_id = wx.NewId() 324 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Move data to another episode'))) 325 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__relink_encounter_data2episode) 326 # - edit encounter details 327 menu_id = wx.NewId() 328 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Edit details'))) 329 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__edit_encounter_details) 330 331 item = self.__enc_context_popup.Append(-1, _('Edit progress notes')) 332 self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item) 333 334 item = self.__enc_context_popup.Append(-1, _('Move progress notes')) 335 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item) 336 337 item = self.__enc_context_popup.Append(-1, _('Export for Medistar')) 338 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item) 339 340 # - health issues 341 self.__issue_context_popup = wx.Menu(title = _('Health Issue Actions:')) 342 343 menu_id = wx.NewId() 344 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Edit details'))) 345 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__edit_issue) 346 347 menu_id = wx.NewId() 348 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Delete'))) 349 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__delete_issue) 350 351 self.__issue_context_popup.AppendSeparator() 352 353 menu_id = wx.NewId() 354 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Open to encounter level'))) 355 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__expand_issue_to_encounter_level) 356 # print " attach issue to another patient" 357 # print " move all episodes to another issue" 358 359 # - root node 360 self.__root_context_popup = wx.Menu(title = _('EMR Actions:')) 361 362 menu_id = wx.NewId() 363 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Create health issue'))) 364 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__create_issue) 365 366 item = self.__root_context_popup.Append(-1, _('Create episode')) 367 self.Bind(wx.EVT_MENU, self.__create_episode, item) 368 369 menu_id = wx.NewId() 370 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage allergies'))) 371 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__document_allergy) 372 373 menu_id = wx.NewId() 374 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage family history'))) 375 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_family_history) 376 377 menu_id = wx.NewId() 378 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage hospitalizations'))) 379 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_hospital_stays) 380 381 menu_id = wx.NewId() 382 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage occupation'))) 383 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_occupation) 384 385 menu_id = wx.NewId() 386 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage procedures'))) 387 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_procedures) 388 389 menu_id = wx.NewId() 390 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage vaccinations'))) 391 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_vaccinations) 392 393 self.__root_context_popup.AppendSeparator() 394 395 # expand tree 396 expand_menu = wx.Menu() 397 self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu) 398 399 menu_id = wx.NewId() 400 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level'))) 401 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level) 402 403 menu_id = wx.NewId() 404 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level'))) 405 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level) 406 407 menu_id = wx.NewId() 408 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level'))) 409 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
410 #--------------------------------------------------------
411 - def __handle_root_context(self, pos=wx.DefaultPosition):
412 self.PopupMenu(self.__root_context_popup, pos)
413 #--------------------------------------------------------
414 - def __handle_issue_context(self, pos=wx.DefaultPosition):
415 # self.__issue_context_popup.SetTitle(_('Episode %s') % episode['description']) 416 self.PopupMenu(self.__issue_context_popup, pos)
417 #--------------------------------------------------------
418 - def __handle_episode_context(self, pos=wx.DefaultPosition):
419 # self.__epi_context_popup.SetTitle(_('Episode %s') % self.__curr_node_data['description']) 420 self.PopupMenu(self.__epi_context_popup, pos)
421 #--------------------------------------------------------
422 - def __handle_encounter_context(self, pos=wx.DefaultPosition):
423 self.PopupMenu(self.__enc_context_popup, pos)
424 #-------------------------------------------------------- 425 # episode level 426 #--------------------------------------------------------
427 - def __move_encounters(self, event):
428 episode = self.GetPyData(self.__curr_node) 429 430 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 431 parent = self, 432 episodes = [episode['pk_episode']], 433 move_all = True 434 )
435 #--------------------------------------------------------
436 - def __edit_episode(self, event):
437 gmEMRStructWidgets.edit_episode(parent = self, episode = self.__curr_node_data)
438 #--------------------------------------------------------
439 - def __promote_episode_to_issue(self, evt):
440 pat = gmPerson.gmCurrentPatient() 441 gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = pat.get_emr())
442 #--------------------------------------------------------
443 - def __delete_episode(self, event):
444 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 445 parent = self, 446 id = -1, 447 caption = _('Deleting episode'), 448 button_defs = [ 449 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')}, 450 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')} 451 ], 452 question = _( 453 'Are you sure you want to delete this episode ?\n' 454 '\n' 455 ' "%s"\n' 456 ) % self.__curr_node_data['description'] 457 ) 458 result = dlg.ShowModal() 459 if result != wx.ID_YES: 460 return 461 462 if not gmEMRStructItems.delete_episode(episode = self.__curr_node_data): 463 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
464 #--------------------------------------------------------
465 - def __expand_episode_node(self, episode_node=None):
466 self.DeleteChildren(episode_node) 467 468 emr = self.__pat.emr 469 epi = self.GetPyData(episode_node) 470 encounters = emr.get_encounters(episodes = [epi['pk_episode']], skip_empty = True) 471 if len(encounters) == 0: 472 self.SetItemHasChildren(episode_node, False) 473 return 474 475 self.SetItemHasChildren(episode_node, True) 476 477 for enc in encounters: 478 label = u'%s: %s' % ( 479 enc['started'].strftime('%Y-%m-%d'), 480 gmTools.unwrap ( 481 gmTools.coalesce ( 482 gmTools.coalesce ( 483 gmTools.coalesce ( 484 enc.get_latest_soap ( # soAp 485 soap_cat = 'a', 486 episode = epi['pk_episode'] 487 ), 488 enc['assessment_of_encounter'] # or AOE 489 ), 490 enc['reason_for_encounter'] # or RFE 491 ), 492 enc['l10n_type'] # or type 493 ), 494 max_length = 40 495 ) 496 ) 497 encounter_node = self.AppendItem(episode_node, label) 498 self.SetItemPyData(encounter_node, enc) 499 # we don't expand encounter nodes (what for ?) 500 self.SetItemHasChildren(encounter_node, False) 501 502 self.SortChildren(episode_node)
503 #-------------------------------------------------------- 504 # encounter level 505 #--------------------------------------------------------
506 - def __move_progress_notes(self, evt):
507 encounter = self.GetPyData(self.__curr_node) 508 node_parent = self.GetItemParent(self.__curr_node) 509 episode = self.GetPyData(node_parent) 510 511 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 512 parent = self, 513 encounters = [encounter['pk_encounter']], 514 episodes = [episode['pk_episode']] 515 )
516 #--------------------------------------------------------
517 - def __edit_progress_notes(self, event):
518 encounter = self.GetPyData(self.__curr_node) 519 node_parent = self.GetItemParent(self.__curr_node) 520 episode = self.GetPyData(node_parent) 521 522 gmNarrativeWidgets.manage_progress_notes ( 523 parent = self, 524 encounters = [encounter['pk_encounter']], 525 episodes = [episode['pk_episode']] 526 )
527 #--------------------------------------------------------
528 - def __edit_encounter_details(self, event):
529 node_data = self.GetPyData(self.__curr_node) 530 gmEMRStructWidgets.edit_encounter(parent = self, encounter = node_data) 531 self.__populate_tree()
532 #-------------------------------------------------------- 550 #-------------------------------------------------------- 551 # issue level 552 #--------------------------------------------------------
553 - def __edit_issue(self, event):
554 gmEMRStructWidgets.edit_health_issue(parent = self, issue = self.__curr_node_data)
555 #--------------------------------------------------------
556 - def __delete_issue(self, event):
557 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 558 parent = self, 559 id = -1, 560 caption = _('Deleting health issue'), 561 button_defs = [ 562 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')}, 563 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')} 564 ], 565 question = _( 566 'Are you sure you want to delete this health issue ?\n' 567 '\n' 568 ' "%s"\n' 569 ) % self.__curr_node_data['description'] 570 ) 571 result = dlg.ShowModal() 572 if result != wx.ID_YES: 573 dlg.Destroy() 574 return 575 576 dlg.Destroy() 577 578 try: 579 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data) 580 except gmExceptions.DatabaseObjectInUseError: 581 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
582 #--------------------------------------------------------
583 - def __expand_issue_to_encounter_level(self, evt):
584 585 if not self.__curr_node.IsOk(): 586 return 587 588 self.Expand(self.__curr_node) 589 590 epi, epi_cookie = self.GetFirstChild(self.__curr_node) 591 while epi.IsOk(): 592 self.Expand(epi) 593 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
594 #--------------------------------------------------------
595 - def __expand_issue_node(self, issue_node=None):
596 self.DeleteChildren(issue_node) 597 598 emr = self.__pat.emr 599 issue = self.GetPyData(issue_node) 600 episodes = emr.get_episodes(issues = [issue['pk_health_issue']]) 601 if len(episodes) == 0: 602 self.SetItemHasChildren(issue_node, False) 603 return 604 605 self.SetItemHasChildren(issue_node, True) 606 607 for episode in episodes: 608 episode_node = self.AppendItem(issue_node, episode['description']) 609 self.SetItemPyData(episode_node, episode) 610 # fake it so we can expand it 611 self.SetItemHasChildren(episode_node, True) 612 613 self.SortChildren(issue_node)
614 #--------------------------------------------------------
615 - def __expand_pseudo_issue_node(self, fake_issue_node=None):
616 self.DeleteChildren(fake_issue_node) 617 618 emr = self.__pat.emr 619 episodes = emr.unlinked_episodes 620 if len(episodes) == 0: 621 self.SetItemHasChildren(fake_issue_node, False) 622 return 623 624 self.SetItemHasChildren(fake_issue_node, True) 625 626 for episode in episodes: 627 episode_node = self.AppendItem(fake_issue_node, episode['description']) 628 self.SetItemPyData(episode_node, episode) 629 if episode['episode_open']: 630 self.SetItemBold(fake_issue_node, True) 631 # fake it so we can expand it 632 self.SetItemHasChildren(episode_node, True) 633 634 self.SortChildren(fake_issue_node)
635 #-------------------------------------------------------- 636 # EMR level 637 #--------------------------------------------------------
638 - def __create_issue(self, event):
639 gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
640 #--------------------------------------------------------
641 - def __create_episode(self, event):
642 gmEMRStructWidgets.edit_episode(parent = self, episode = None)
643 #--------------------------------------------------------
644 - def __document_allergy(self, event):
645 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1) 646 # FIXME: use signal and use node level update 647 if dlg.ShowModal() == wx.ID_OK: 648 self.__populate_tree() 649 dlg.Destroy() 650 return
651 #--------------------------------------------------------
652 - def __manage_procedures(self, event):
654 #--------------------------------------------------------
655 - def __manage_family_history(self, event):
657 #--------------------------------------------------------
658 - def __manage_hospital_stays(self, event):
660 #--------------------------------------------------------
661 - def __manage_occupation(self, event):
663 #--------------------------------------------------------
664 - def __manage_vaccinations(self, event):
665 gmVaccWidgets.manage_vaccinations(parent = self)
666 #--------------------------------------------------------
667 - def __expand_to_issue_level(self, evt):
668 669 root_item = self.GetRootItem() 670 671 if not root_item.IsOk(): 672 return 673 674 self.Expand(root_item) 675 676 # collapse episodes and issues 677 issue, issue_cookie = self.GetFirstChild(root_item) 678 while issue.IsOk(): 679 self.Collapse(issue) 680 epi, epi_cookie = self.GetFirstChild(issue) 681 while epi.IsOk(): 682 self.Collapse(epi) 683 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 684 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
685 #--------------------------------------------------------
686 - def __expand_to_episode_level(self, evt):
687 688 root_item = self.GetRootItem() 689 690 if not root_item.IsOk(): 691 return 692 693 self.Expand(root_item) 694 695 # collapse episodes, expand issues 696 issue, issue_cookie = self.GetFirstChild(root_item) 697 while issue.IsOk(): 698 self.Expand(issue) 699 epi, epi_cookie = self.GetFirstChild(issue) 700 while epi.IsOk(): 701 self.Collapse(epi) 702 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 703 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
704 #--------------------------------------------------------
705 - def __expand_to_encounter_level(self, evt):
706 707 root_item = self.GetRootItem() 708 709 if not root_item.IsOk(): 710 return 711 712 self.Expand(root_item) 713 714 # collapse episodes, expand issues 715 issue, issue_cookie = self.GetFirstChild(root_item) 716 while issue.IsOk(): 717 self.Expand(issue) 718 epi, epi_cookie = self.GetFirstChild(issue) 719 while epi.IsOk(): 720 self.Expand(epi) 721 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 722 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
723 #--------------------------------------------------------
724 - def __export_encounter_for_medistar(self, evt):
725 gmNarrativeWidgets.export_narrative_for_medistar_import ( 726 parent = self, 727 soap_cats = u'soapu', 728 encounter = self.__curr_node_data 729 )
730 #--------------------------------------------------------
731 - def __expand_root_node(self):
732 root_node = self.GetRootItem() 733 self.DeleteChildren(root_node) 734 735 issues = [{ 736 'description': _('Unattributed episodes'), 737 'has_open_episode': False, 738 'pk_health_issue': None 739 }] 740 741 emr = self.__pat.emr 742 issues.extend(emr.health_issues) 743 744 for issue in issues: 745 issue_node = self.AppendItem(root_node, issue['description']) 746 self.SetItemBold(issue_node, issue['has_open_episode']) 747 self.SetItemPyData(issue_node, issue) 748 # fake it so we can expand it 749 self.SetItemHasChildren(issue_node, True) 750 751 self.SetItemHasChildren(root_node, (len(issues) != 0)) 752 self.SortChildren(root_node)
753 #-------------------------------------------------------- 754 # event handlers 755 #--------------------------------------------------------
756 - def _on_narrative_mod_db(self, *args, **kwargs):
757 wx.CallAfter(self.__update_text_for_selected_node)
758 #--------------------------------------------------------
759 - def _on_episode_mod_db(self, *args, **kwargs):
760 wx.CallAfter(self.__populate_tree)
761 #--------------------------------------------------------
762 - def _on_issue_mod_db(self, *args, **kwargs):
763 wx.CallAfter(self.__populate_tree)
764 #--------------------------------------------------------
765 - def _on_tree_item_expanding(self, event):
766 if not self.__pat.connected: 767 return 768 769 event.Skip() 770 771 node = event.GetItem() 772 if node == self.GetRootItem(): 773 self.__expand_root_node() 774 return 775 776 node_data = self.GetPyData(node) 777 778 if isinstance(node_data, gmEMRStructItems.cHealthIssue): 779 self.__expand_issue_node(issue_node = node) 780 return 781 782 if isinstance(node_data, gmEMRStructItems.cEpisode): 783 self.__expand_episode_node(episode_node = node) 784 return 785 786 # pseudo node "free-standing episodes" 787 if type(node_data) == type({}): 788 self.__expand_pseudo_issue_node(fake_issue_node = node) 789 return
790 791 # encounter nodes do not need expanding 792 #if isinstance(node_data, gmEMRStructItems.cEncounter): 793 #--------------------------------------------------------
794 - def _on_tree_item_selected(self, event):
795 sel_item = event.GetItem() 796 self.__curr_node = sel_item 797 self.__update_text_for_selected_node() 798 return True
799 # #-------------------------------------------------------- 800 # def _on_mouse_motion(self, event): 801 # 802 # cursor_pos = (event.GetX(), event.GetY()) 803 # 804 # self.SetToolTipString(u'') 805 # 806 # if cursor_pos != self._old_cursor_pos: 807 # self._old_cursor_pos = cursor_pos 808 # (item, flags) = self.HitTest(cursor_pos) 809 # #if flags != wx.TREE_HITTEST_NOWHERE: 810 # if flags == wx.TREE_HITTEST_ONITEMLABEL: 811 # data = self.GetPyData(item) 812 # 813 # if not isinstance(data, gmEMRStructItems.cEncounter): 814 # return 815 # 816 # self.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 817 # data['started'].strftime('%x'), 818 # data['l10n_type'], 819 # data['started'].strftime('%H:%m'), 820 # data['last_affirmed'].strftime('%H:%m'), 821 # gmTools.coalesce(data['reason_for_encounter'], u''), 822 # gmTools.coalesce(data['assessment_of_encounter'], u'') 823 # )) 824 #--------------------------------------------------------
825 - def _on_tree_item_gettooltip(self, event):
826 827 item = event.GetItem() 828 829 if not item.IsOk(): 830 event.SetToolTip(u' ') 831 return 832 833 data = self.GetPyData(item) 834 835 if isinstance(data, gmEMRStructItems.cEncounter): 836 tt = u'%s %s %s - %s\n' % ( 837 data['started'].strftime('%x'), 838 data['l10n_type'], 839 data['started'].strftime('%H:%M'), 840 data['last_affirmed'].strftime('%H:%M') 841 ) 842 if data['reason_for_encounter'] is not None: 843 tt += u'\n' 844 tt += _('RFE: %s') % data['reason_for_encounter'] 845 if len(data['pk_generic_codes_rfe']) > 0: 846 for code in data.generic_codes_rfe: 847 tt += u'\n %s: %s%s%s\n (%s %s)' % ( 848 code['code'], 849 gmTools.u_left_double_angle_quote, 850 code['term'], 851 gmTools.u_right_double_angle_quote, 852 code['name_short'], 853 code['version'] 854 ) 855 if data['assessment_of_encounter'] is not None: 856 tt += u'\n' 857 tt += _('AOE: %s') % data['assessment_of_encounter'] 858 if len(data['pk_generic_codes_aoe']) > 0: 859 for code in data.generic_codes_aoe: 860 tt += u'\n %s: %s%s%s\n (%s %s)' % ( 861 code['code'], 862 gmTools.u_left_double_angle_quote, 863 code['term'], 864 gmTools.u_right_double_angle_quote, 865 code['name_short'], 866 code['version'] 867 ) 868 869 elif isinstance(data, gmEMRStructItems.cEpisode): 870 tt = u'' 871 tt += gmTools.bool2subst ( 872 (data['diagnostic_certainty_classification'] is not None), 873 data.diagnostic_certainty_description + u'\n\n', 874 u'' 875 ) 876 tt += gmTools.bool2subst ( 877 data['episode_open'], 878 _('ongoing episode'), 879 _('closed episode'), 880 'error: episode state is None' 881 ) + u'\n' 882 tt += gmTools.coalesce(data['summary'], u'', u'\n%s') 883 if len(data['pk_generic_codes']) > 0: 884 tt += u'\n' 885 for code in data.generic_codes: 886 tt += u'%s: %s%s%s\n (%s %s)\n' % ( 887 code['code'], 888 gmTools.u_left_double_angle_quote, 889 code['term'], 890 gmTools.u_right_double_angle_quote, 891 code['name_short'], 892 code['version'] 893 ) 894 895 tt = tt.strip(u'\n') 896 if tt == u'': 897 tt = u' ' 898 899 elif isinstance(data, gmEMRStructItems.cHealthIssue): 900 tt = u'' 901 tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), u'') 902 tt += gmTools.bool2subst ( 903 (data['diagnostic_certainty_classification'] is not None), 904 data.diagnostic_certainty_description + u'\n', 905 u'' 906 ) 907 tt += gmTools.bool2subst ( 908 (data['laterality'] not in [None, u'na']), 909 data.laterality_description + u'\n', 910 u'' 911 ) 912 # noted_at_age is too costly 913 tt += gmTools.bool2subst(data['is_active'], _('active') + u'\n', u'') 914 tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + u'\n', u'') 915 tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + u'\n', u'') 916 tt += gmTools.coalesce(data['grouping'], u'\n', _('Grouping: %s') + u'\n') 917 tt += gmTools.coalesce(data['summary'], u'', u'\n%s') 918 if len(data['pk_generic_codes']) > 0: 919 tt += u'\n' 920 for code in data.generic_codes: 921 tt += u'%s: %s%s%s\n (%s %s)\n' % ( 922 code['code'], 923 gmTools.u_left_double_angle_quote, 924 code['term'], 925 gmTools.u_right_double_angle_quote, 926 code['name_short'], 927 code['version'] 928 ) 929 930 tt = tt.strip(u'\n') 931 if tt == u'': 932 tt = u' ' 933 934 else: 935 tt = self.__root_tooltip 936 937 event.SetToolTip(tt)
938 939 # doing this prevents the tooltip from showing at all 940 #event.Skip() 941 942 #widgetXY.GetToolTip().Enable(False) 943 # 944 #seems to work, supposing the tooltip is actually set for the widget, 945 #otherwise a test would be needed 946 #if widgetXY.GetToolTip(): 947 # widgetXY.GetToolTip().Enable(False) 948 #--------------------------------------------------------
949 - def _on_tree_item_right_clicked(self, event):
950 """Right button clicked: display the popup for the tree""" 951 952 node = event.GetItem() 953 self.SelectItem(node) 954 self.__curr_node_data = self.GetPyData(node) 955 self.__curr_node = node 956 957 pos = wx.DefaultPosition 958 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue): 959 self.__handle_issue_context(pos=pos) 960 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode): 961 self.__handle_episode_context(pos=pos) 962 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter): 963 self.__handle_encounter_context(pos=pos) 964 elif node == self.GetRootItem(): 965 self.__handle_root_context() 966 elif type(self.__curr_node_data) == type({}): 967 # ignore pseudo node "free-standing episodes" 968 pass 969 else: 970 print "error: unknown node type, no popup menu" 971 event.Skip()
972 #--------------------------------------------------------
973 - def OnCompareItems (self, node1=None, node2=None):
974 """Used in sorting items. 975 976 -1: 1 < 2 977 0: 1 = 2 978 1: 1 > 2 979 """ 980 # FIXME: implement sort modes, chron, reverse cron, by regex, etc 981 982 if not node1: 983 _log.debug('invalid node 1') 984 return 0 985 if not node2: 986 _log.debug('invalid node 2') 987 return 0 988 989 if not node1.IsOk(): 990 _log.debug('invalid node 1') 991 return 0 992 if not node2.IsOk(): 993 _log.debug('invalid node 2') 994 return 0 995 996 item1 = self.GetPyData(node1) 997 item2 = self.GetPyData(node2) 998 999 # dummy health issue always on top 1000 if isinstance(item1, type({})): 1001 return -1 1002 if isinstance(item2, type({})): 1003 return 1 1004 1005 # encounters: reverse chronologically 1006 if isinstance(item1, gmEMRStructItems.cEncounter): 1007 if item1['started'] == item2['started']: 1008 return 0 1009 if item1['started'] > item2['started']: 1010 return -1 1011 return 1 1012 1013 # episodes: chronologically 1014 if isinstance(item1, gmEMRStructItems.cEpisode): 1015 start1 = item1.best_guess_start_date 1016 start2 = item2.best_guess_start_date 1017 if start1 == start2: 1018 return 0 1019 if start1 < start2: 1020 return -1 1021 return 1 1022 1023 # issues: alpha by grouping, no grouping at the bottom 1024 if isinstance(item1, gmEMRStructItems.cHealthIssue): 1025 1026 # no grouping below grouping 1027 if item1['grouping'] is None: 1028 if item2['grouping'] is not None: 1029 return 1 1030 1031 # grouping above no grouping 1032 if item1['grouping'] is not None: 1033 if item2['grouping'] is None: 1034 return -1 1035 1036 # both no grouping: alpha on description 1037 if (item1['grouping'] is None) and (item2['grouping'] is None): 1038 if item1['description'].lower() < item2['description'].lower(): 1039 return -1 1040 if item1['description'].lower() > item2['description'].lower(): 1041 return 1 1042 return 0 1043 1044 # both with grouping: alpha on grouping, then alpha on description 1045 if item1['grouping'] < item2['grouping']: 1046 return -1 1047 1048 if item1['grouping'] > item2['grouping']: 1049 return 1 1050 1051 if item1['description'].lower() < item2['description'].lower(): 1052 return -1 1053 1054 if item1['description'].lower() > item2['description'].lower(): 1055 return 1 1056 1057 return 0 1058 1059 _log.error('unknown item type during sorting EMR tree:') 1060 _log.error('item1: %s', type(item1)) 1061 _log.error('item2: %s', type(item2)) 1062 1063 return 0
1064 #-------------------------------------------------------- 1065 # properties 1066 #--------------------------------------------------------
1067 - def _get_details_display_mode(self):
1068 return self.__details_display_mode
1069
1070 - def _set_details_display_mode(self, mode):
1071 if mode not in [u'details', u'journal']: 1072 raise ValueError('details display mode must be one of "details", "journal"') 1073 if self.__details_display_mode == mode: 1074 return 1075 self.__details_display_mode = mode 1076 self.__update_text_for_selected_node()
1077 1078 details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
1079 #================================================================ 1080 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl 1081
1082 -class cScrolledEMRTreePnl(wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl):
1083 """A scrollable panel holding an EMR tree. 1084 1085 Lacks a widget to display details for selected items. The 1086 tree data will be refetched - if necessary - whenever 1087 repopulate_ui() is called, e.g., when then patient is changed. 1088 """
1089 - def __init__(self, *args, **kwds):
1091 #--------------------------------------------------------
1092 - def repopulate_ui(self):
1093 self._emr_tree.refresh() 1094 return True
1095 #============================================================ 1096 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl 1097
1098 -class cSplittedEMRTreeBrowserPnl(wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl):
1099 """A splitter window holding an EMR tree. 1100 1101 The left hand side displays a scrollable EMR tree while 1102 on the right details for selected items are displayed. 1103 1104 Expects to be put into a Notebook. 1105 """
1106 - def __init__(self, *args, **kwds):
1107 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds) 1108 self._pnl_emr_tree._emr_tree.set_narrative_display(narrative_display = self._TCTRL_item_details) 1109 self._pnl_emr_tree._emr_tree.set_image_display(image_display = self._PNL_visual_soap) 1110 self._pnl_emr_tree._emr_tree.set_enable_display_mode_selection_callback(self.enable_display_mode_selection) 1111 self.__register_events()
1112 #--------------------------------------------------------
1113 - def __register_events(self):
1114 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1115 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 1116 return True
1117 #-------------------------------------------------------- 1118 # event handler 1119 #--------------------------------------------------------
1120 - def _on_pre_patient_selection(self):
1121 self._pnl_emr_tree._emr_tree.clear_tree() 1122 return True
1123 #--------------------------------------------------------
1124 - def _on_post_patient_selection(self):
1125 wx.CallAfter(self.__on_post_patient_selection) 1126 return True
1127 #--------------------------------------------------------
1128 - def __on_post_patient_selection(self):
1129 if self.GetParent().GetCurrentPage() != self: 1130 return True 1131 self.repopulate_ui()
1132 #--------------------------------------------------------
1133 - def _on_show_details_selected(self, event):
1134 self._pnl_emr_tree._emr_tree.details_display_mode = u'details'
1135 #--------------------------------------------------------
1136 - def _on_show_journal_selected(self, event):
1137 self._pnl_emr_tree._emr_tree.details_display_mode = u'journal'
1138 #-------------------------------------------------------- 1139 # external API 1140 #--------------------------------------------------------
1141 - def repopulate_ui(self):
1142 """Fills UI with data.""" 1143 self._pnl_emr_tree.repopulate_ui() 1144 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True) 1145 return True
1146 #--------------------------------------------------------
1147 - def enable_display_mode_selection(self, enable):
1148 if enable: 1149 self._RBTN_details.Enable(True) 1150 self._RBTN_journal.Enable(True) 1151 return 1152 self._RBTN_details.Enable(False) 1153 self._RBTN_journal.Enable(False)
1154 #================================================================
1155 -class cEMRJournalPanel(wx.Panel):
1156 - def __init__(self, *args, **kwargs):
1157 wx.Panel.__init__(self, *args, **kwargs) 1158 1159 self.__do_layout() 1160 self.__register_events()
1161 #--------------------------------------------------------
1162 - def __do_layout(self):
1163 self.__journal = wx.TextCtrl ( 1164 self, 1165 -1, 1166 _('No EMR data loaded.'), 1167 style = wx.TE_MULTILINE | wx.TE_READONLY 1168 ) 1169 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)) 1170 # arrange widgets 1171 szr_outer = wx.BoxSizer(wx.VERTICAL) 1172 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0) 1173 # and do layout 1174 self.SetAutoLayout(1) 1175 self.SetSizer(szr_outer) 1176 szr_outer.Fit(self) 1177 szr_outer.SetSizeHints(self) 1178 self.Layout()
1179 #--------------------------------------------------------
1180 - def __register_events(self):
1181 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1182 #--------------------------------------------------------
1183 - def _on_post_patient_selection(self):
1184 """Expects to be in a Notebook.""" 1185 if self.GetParent().GetCurrentPage() == self: 1186 self.repopulate_ui() 1187 return True
1188 #-------------------------------------------------------- 1189 # notebook plugin API 1190 #--------------------------------------------------------
1191 - def repopulate_ui(self):
1192 txt = StringIO.StringIO() 1193 exporter = gmPatientExporter.cEMRJournalExporter() 1194 # FIXME: if journal is large this will error out, use generator/yield etc 1195 # FIXME: turn into proper list 1196 try: 1197 exporter.export(txt) 1198 self.__journal.SetValue(txt.getvalue()) 1199 except ValueError: 1200 _log.exception('cannot get EMR journal') 1201 self.__journal.SetValue (_( 1202 'An error occurred while retrieving the EMR\n' 1203 'in journal form for the active patient.\n\n' 1204 'Please check the log file for details.' 1205 )) 1206 txt.close() 1207 self.__journal.ShowPosition(self.__journal.GetLastPosition()) 1208 return True
1209 #================================================================ 1210 # MAIN 1211 #---------------------------------------------------------------- 1212 if __name__ == '__main__': 1213 1214 _log.info("starting emr browser...") 1215 1216 try: 1217 # obtain patient 1218 patient = gmPersonSearch.ask_for_patient() 1219 if patient is None: 1220 print "No patient. Exiting gracefully..." 1221 sys.exit(0) 1222 gmPatSearchWidgets.set_active_patient(patient = patient) 1223 1224 # display standalone browser 1225 application = wx.PyWidgetTester(size=(800,600)) 1226 emr_browser = cEMRBrowserPanel(application.frame, -1) 1227 emr_browser.refresh_tree() 1228 1229 application.frame.Show(True) 1230 application.MainLoop() 1231 1232 # clean up 1233 if patient is not None: 1234 try: 1235 patient.cleanup() 1236 except: 1237 print "error cleaning up patient" 1238 except StandardError: 1239 _log.exception("unhandled exception caught !") 1240 # but re-raise them 1241 raise 1242 1243 _log.info("closing emr browser...") 1244 1245 #================================================================ 1246