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

Source Code for Module Gnumed.wxpython.gmEMRBrowser

  1  """GNUmed patient EMR tree browser. 
  2  """ 
  3  #================================================================ 
  4  __version__ = "$Revision: 1.111 $" 
  5  __author__ = "cfmoro1976@yahoo.es, sjtan@swiftdsl.com.au, Karsten.Hilbert@gmx.net" 
  6  __license__ = "GPL" 
  7   
  8  # std lib 
  9  import sys, types, os.path, StringIO, codecs, logging 
 10   
 11   
 12  # 3rd party 
 13  import wx 
 14   
 15   
 16  # GNUmed libs 
 17  from Gnumed.pycommon import gmI18N, gmDispatcher, gmExceptions, gmTools 
 18  from Gnumed.exporters import gmPatientExporter 
 19  from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter 
 20  from Gnumed.wxpython import gmGuiHelpers, gmEMRStructWidgets, gmSOAPWidgets 
 21  from Gnumed.wxpython import gmAllergyWidgets, gmNarrativeWidgets, gmPatSearchWidgets 
 22  from Gnumed.wxpython import gmDemographicsWidgets, gmVaccWidgets 
 23   
 24   
 25  _log = logging.getLogger('gm.ui') 
 26  _log.info(__version__) 
 27   
 28  #============================================================ 
29 -def export_emr_to_ascii(parent=None):
30 """ 31 Dump the patient's EMR from GUI client 32 @param parent - The parent widget 33 @type parent - A wx.Window instance 34 """ 35 # sanity checks 36 if parent is None: 37 raise TypeError('expected wx.Window instance as parent, got <None>') 38 39 pat = gmPerson.gmCurrentPatient() 40 if not pat.connected: 41 gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.')) 42 return False 43 44 # get file name 45 wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files")) 46 defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))) 47 gmTools.mkdir(defdir) 48 fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames']) 49 dlg = wx.FileDialog ( 50 parent = parent, 51 message = _("Save patient's EMR as..."), 52 defaultDir = defdir, 53 defaultFile = fname, 54 wildcard = wc, 55 style = wx.SAVE 56 ) 57 choice = dlg.ShowModal() 58 fname = dlg.GetPath() 59 dlg.Destroy() 60 if choice != wx.ID_OK: 61 return None 62 63 _log.debug('exporting EMR to [%s]', fname) 64 65 # output_file = open(fname, 'wb') 66 output_file = codecs.open(fname, 'wb', encoding='utf8', errors='replace') 67 exporter = gmPatientExporter.cEmrExport(patient = pat) 68 exporter.set_output_file(output_file) 69 exporter.dump_constraints() 70 exporter.dump_demographic_record(True) 71 exporter.dump_clinical_record() 72 exporter.dump_med_docs() 73 output_file.close() 74 75 gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False) 76 return fname
77 #============================================================
78 -class cEMRTree(wx.TreeCtrl, gmGuiHelpers.cTreeExpansionHistoryMixin):
79 """This wx.TreeCtrl derivative displays a tree view of the medical record.""" 80 81 #--------------------------------------------------------
82 - def __init__(self, parent, id, *args, **kwds):
83 """Set up our specialised tree. 84 """ 85 kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER 86 wx.TreeCtrl.__init__(self, parent, id, *args, **kwds) 87 88 gmGuiHelpers.cTreeExpansionHistoryMixin.__init__(self) 89 90 try: 91 self.__narr_display = kwds['narr_display'] 92 del kwds['narr_display'] 93 except KeyError: 94 self.__narr_display = None 95 self.__pat = gmPerson.gmCurrentPatient() 96 self.__curr_node = None 97 self.__exporter = gmPatientExporter.cEmrExport(patient = self.__pat) 98 99 self._old_cursor_pos = None 100 101 self.__make_popup_menus() 102 self.__register_events()
103 #-------------------------------------------------------- 104 # external API 105 #--------------------------------------------------------
106 - def refresh(self):
107 if not self.__pat.connected: 108 gmDispatcher.send(signal='statustext', msg=_('Cannot load clinical narrative. No active patient.'),) 109 return False 110 111 if not self.__populate_tree(): 112 return False 113 114 return True
115 #--------------------------------------------------------
116 - def set_narrative_display(self, narrative_display=None):
117 self.__narr_display = narrative_display
118 #-------------------------------------------------------- 119 # internal helpers 120 #--------------------------------------------------------
121 - def __register_events(self):
122 """Configures enabled event signals.""" 123 wx.EVT_TREE_SEL_CHANGED (self, self.GetId(), self._on_tree_item_selected) 124 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self._on_tree_item_right_clicked) 125 126 # handle tooltips 127 # wx.EVT_MOTION(self, self._on_mouse_motion) 128 wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip) 129 130 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db) 131 gmDispatcher.connect(signal = 'episode_mod_db', receiver = self._on_episode_mod_db) 132 gmDispatcher.connect(signal = 'health_issue_mod_db', receiver = self._on_issue_mod_db)
133 #--------------------------------------------------------
134 - def __populate_tree(self):
135 """Updates EMR browser data.""" 136 # FIXME: auto select the previously self.__curr_node if not None 137 # FIXME: error handling 138 139 wx.BeginBusyCursor() 140 141 self.snapshot_expansion() 142 143 # init new tree 144 self.DeleteAllItems() 145 root_item = self.AddRoot(_('EMR of %s') % self.__pat['description']) 146 self.SetPyData(root_item, None) 147 self.SetItemHasChildren(root_item, True) 148 149 # have the tree filled by the exporter 150 self.__exporter.get_historical_tree(self) 151 self.__curr_node = root_item 152 153 self.SelectItem(root_item) 154 self.Expand(root_item) 155 self.__update_text_for_selected_node() 156 157 self.restore_expansion() 158 159 wx.EndBusyCursor() 160 return True
161 #--------------------------------------------------------
163 """Displays information for the selected tree node.""" 164 165 if self.__narr_display is None: 166 return 167 168 if self.__curr_node is None: 169 return 170 171 node_data = self.GetPyData(self.__curr_node) 172 173 # update displayed text 174 if isinstance(node_data, (gmEMRStructItems.cHealthIssue, types.DictType)): 175 # FIXME: turn into real dummy issue 176 if node_data['pk_health_issue'] is None: 177 txt = _('Pool of unassociated episodes:\n\n "%s"') % node_data['description'] 178 else: 179 txt = node_data.format(left_margin=1, patient = self.__pat) 180 181 elif isinstance(node_data, gmEMRStructItems.cEpisode): 182 txt = node_data.format(left_margin = 1, patient = self.__pat) 183 184 elif isinstance(node_data, gmEMRStructItems.cEncounter): 185 epi = self.GetPyData(self.GetItemParent(self.__curr_node)) 186 txt = node_data.format(episodes = [epi['pk_episode']], with_soap = True, left_margin = 1, patient = self.__pat) 187 188 else: 189 emr = self.__pat.get_emr() 190 txt = emr.format_summary() 191 192 self.__narr_display.Clear() 193 self.__narr_display.WriteText(txt)
194 #--------------------------------------------------------
195 - def __make_popup_menus(self):
196 197 # - episodes 198 self.__epi_context_popup = wx.Menu(title = _('Episode Menu')) 199 200 menu_id = wx.NewId() 201 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Edit details'))) 202 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__edit_episode) 203 204 menu_id = wx.NewId() 205 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Delete'))) 206 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__delete_episode) 207 208 menu_id = wx.NewId() 209 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Promote'))) 210 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__promote_episode_to_issue) 211 212 menu_id = wx.NewId() 213 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Move encounters'))) 214 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__move_encounters) 215 216 # - encounters 217 self.__enc_context_popup = wx.Menu(title = _('Encounter Menu')) 218 # - move data 219 menu_id = wx.NewId() 220 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Move data to another episode'))) 221 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__relink_encounter_data2episode) 222 # - edit encounter details 223 menu_id = wx.NewId() 224 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Edit details'))) 225 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__edit_encounter_details) 226 227 item = self.__enc_context_popup.Append(-1, _('Edit progress notes')) 228 self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item) 229 230 item = self.__enc_context_popup.Append(-1, _('Move progress notes')) 231 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item) 232 233 item = self.__enc_context_popup.Append(-1, _('Export for Medistar')) 234 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item) 235 236 # - health issues 237 self.__issue_context_popup = wx.Menu(title = _('Health Issue Menu')) 238 239 menu_id = wx.NewId() 240 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Edit details'))) 241 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__edit_issue) 242 243 menu_id = wx.NewId() 244 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Delete'))) 245 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__delete_issue) 246 247 self.__issue_context_popup.AppendSeparator() 248 249 menu_id = wx.NewId() 250 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Open to encounter level'))) 251 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__expand_issue_to_encounter_level) 252 # print " attach issue to another patient" 253 # print " move all episodes to another issue" 254 255 # - root node 256 self.__root_context_popup = wx.Menu(title = _('EMR Menu')) 257 258 menu_id = wx.NewId() 259 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Create health issue'))) 260 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__create_issue) 261 262 menu_id = wx.NewId() 263 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage allergies'))) 264 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__document_allergy) 265 266 menu_id = wx.NewId() 267 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage vaccinations'))) 268 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_vaccinations) 269 270 menu_id = wx.NewId() 271 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage procedures'))) 272 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_procedures) 273 274 menu_id = wx.NewId() 275 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage hospitalizations'))) 276 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_hospital_stays) 277 278 menu_id = wx.NewId() 279 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage occupation'))) 280 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_occupation) 281 282 self.__root_context_popup.AppendSeparator() 283 284 # expand tree 285 expand_menu = wx.Menu() 286 self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu) 287 288 menu_id = wx.NewId() 289 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level'))) 290 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level) 291 292 menu_id = wx.NewId() 293 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level'))) 294 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level) 295 296 menu_id = wx.NewId() 297 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level'))) 298 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
299 #--------------------------------------------------------
300 - def __handle_root_context(self, pos=wx.DefaultPosition):
301 self.PopupMenu(self.__root_context_popup, pos)
302 #--------------------------------------------------------
303 - def __handle_issue_context(self, pos=wx.DefaultPosition):
304 # self.__issue_context_popup.SetTitle(_('Episode %s') % episode['description']) 305 self.PopupMenu(self.__issue_context_popup, pos)
306 #--------------------------------------------------------
307 - def __handle_episode_context(self, pos=wx.DefaultPosition):
308 self.__epi_context_popup.SetTitle(_('Episode %s') % self.__curr_node_data['description']) 309 self.PopupMenu(self.__epi_context_popup, pos)
310 #--------------------------------------------------------
311 - def __handle_encounter_context(self, pos=wx.DefaultPosition):
312 self.PopupMenu(self.__enc_context_popup, pos)
313 #-------------------------------------------------------- 314 # episode level 315 #--------------------------------------------------------
316 - def __move_encounters(self, event):
317 episode = self.GetPyData(self.__curr_node) 318 319 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 320 parent = self, 321 episodes = [episode['pk_episode']], 322 move_all = True 323 )
324 #--------------------------------------------------------
325 - def __edit_episode(self, event):
326 gmEMRStructWidgets.edit_episode(parent = self, episode = self.__curr_node_data)
327 #--------------------------------------------------------
328 - def __promote_episode_to_issue(self, evt):
329 pat = gmPerson.gmCurrentPatient() 330 gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = pat.get_emr())
331 #--------------------------------------------------------
332 - def __delete_episode(self, event):
333 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 334 parent = self, 335 id = -1, 336 caption = _('Deleting episode'), 337 button_defs = [ 338 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')}, 339 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')} 340 ], 341 question = _( 342 'Are you sure you want to delete this episode ?\n' 343 '\n' 344 ' "%s"\n' 345 ) % self.__curr_node_data['description'] 346 ) 347 result = dlg.ShowModal() 348 if result != wx.ID_YES: 349 return 350 351 try: 352 gmEMRStructItems.delete_episode(episode = self.__curr_node_data) 353 except gmExceptions.DatabaseObjectInUseError: 354 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.')) 355 return
356 #-------------------------------------------------------- 357 # encounter level 358 #--------------------------------------------------------
359 - def __move_progress_notes(self, evt):
360 encounter = self.GetPyData(self.__curr_node) 361 node_parent = self.GetItemParent(self.__curr_node) 362 episode = self.GetPyData(node_parent) 363 364 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 365 parent = self, 366 encounters = [encounter['pk_encounter']], 367 episodes = [episode['pk_episode']] 368 )
369 #--------------------------------------------------------
370 - def __edit_progress_notes(self, event):
371 encounter = self.GetPyData(self.__curr_node) 372 node_parent = self.GetItemParent(self.__curr_node) 373 episode = self.GetPyData(node_parent) 374 375 gmNarrativeWidgets.manage_progress_notes ( 376 parent = self, 377 encounters = [encounter['pk_encounter']], 378 episodes = [episode['pk_episode']] 379 )
380 #--------------------------------------------------------
381 - def __edit_encounter_details(self, event):
382 node_data = self.GetPyData(self.__curr_node) 383 dlg = gmEMRStructWidgets.cEncounterEditAreaDlg(parent=self, encounter=node_data) 384 dlg.ShowModal() 385 dlg.Destroy() 386 self.__populate_tree()
387 #-------------------------------------------------------- 405 #-------------------------------------------------------- 406 # issue level 407 #--------------------------------------------------------
408 - def __edit_issue(self, event):
409 gmEMRStructWidgets.edit_health_issue(parent = self, issue = self.__curr_node_data)
410 #--------------------------------------------------------
411 - def __delete_issue(self, event):
412 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 413 parent = self, 414 id = -1, 415 caption = _('Deleting health issue'), 416 button_defs = [ 417 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')}, 418 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')} 419 ], 420 question = _( 421 'Are you sure you want to delete this health issue ?\n' 422 '\n' 423 ' "%s"\n' 424 ) % self.__curr_node_data['description'] 425 ) 426 result = dlg.ShowModal() 427 if result != wx.ID_YES: 428 dlg.Destroy() 429 return 430 431 dlg.Destroy() 432 433 try: 434 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data) 435 except gmExceptions.DatabaseObjectInUseError: 436 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
437 #--------------------------------------------------------
439 440 if not self.__curr_node.IsOk(): 441 return 442 443 self.Expand(self.__curr_node) 444 445 epi, epi_cookie = self.GetFirstChild(self.__curr_node) 446 while epi.IsOk(): 447 self.Expand(epi) 448 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
449 #-------------------------------------------------------- 450 # EMR level 451 #--------------------------------------------------------
452 - def __create_issue(self, event):
453 gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
454 #--------------------------------------------------------
455 - def __document_allergy(self, event):
456 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1) 457 # FIXME: use signal and use node level update 458 if dlg.ShowModal() == wx.ID_OK: 459 self.__populate_tree() 460 dlg.Destroy() 461 return
462 #--------------------------------------------------------
463 - def __manage_procedures(self, event):
465 #--------------------------------------------------------
466 - def __manage_hospital_stays(self, event):
468 #--------------------------------------------------------
469 - def __manage_occupation(self, event):
471 #--------------------------------------------------------
472 - def __manage_vaccinations(self, event):
474 #--------------------------------------------------------
475 - def __expand_to_issue_level(self, evt):
476 477 root_item = self.GetRootItem() 478 479 if not root_item.IsOk(): 480 return 481 482 self.Expand(root_item) 483 484 # collapse episodes and issues 485 issue, issue_cookie = self.GetFirstChild(root_item) 486 while issue.IsOk(): 487 self.Collapse(issue) 488 epi, epi_cookie = self.GetFirstChild(issue) 489 while epi.IsOk(): 490 self.Collapse(epi) 491 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 492 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
493 #--------------------------------------------------------
494 - def __expand_to_episode_level(self, evt):
495 496 root_item = self.GetRootItem() 497 498 if not root_item.IsOk(): 499 return 500 501 self.Expand(root_item) 502 503 # collapse episodes, expand issues 504 issue, issue_cookie = self.GetFirstChild(root_item) 505 while issue.IsOk(): 506 self.Expand(issue) 507 epi, epi_cookie = self.GetFirstChild(issue) 508 while epi.IsOk(): 509 self.Collapse(epi) 510 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 511 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
512 #--------------------------------------------------------
513 - def __expand_to_encounter_level(self, evt):
514 515 root_item = self.GetRootItem() 516 517 if not root_item.IsOk(): 518 return 519 520 self.Expand(root_item) 521 522 # collapse episodes, expand issues 523 issue, issue_cookie = self.GetFirstChild(root_item) 524 while issue.IsOk(): 525 self.Expand(issue) 526 epi, epi_cookie = self.GetFirstChild(issue) 527 while epi.IsOk(): 528 self.Expand(epi) 529 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 530 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
531 #--------------------------------------------------------
532 - def __export_encounter_for_medistar(self, evt):
533 gmNarrativeWidgets.export_narrative_for_medistar_import ( 534 parent = self, 535 soap_cats = u'soap', 536 encounter = self.__curr_node_data 537 )
538 #-------------------------------------------------------- 539 # event handlers 540 #--------------------------------------------------------
541 - def _on_narrative_mod_db(self, *args, **kwargs):
542 wx.CallAfter(self.__update_text_for_selected_node)
543 #--------------------------------------------------------
544 - def _on_episode_mod_db(self, *args, **kwargs):
545 wx.CallAfter(self.__populate_tree)
546 #--------------------------------------------------------
547 - def _on_issue_mod_db(self, *args, **kwargs):
548 wx.CallAfter(self.__populate_tree)
549 #--------------------------------------------------------
550 - def _on_tree_item_selected(self, event):
551 sel_item = event.GetItem() 552 self.__curr_node = sel_item 553 self.__update_text_for_selected_node() 554 return True
555 # #-------------------------------------------------------- 556 # def _on_mouse_motion(self, event): 557 # 558 # cursor_pos = (event.GetX(), event.GetY()) 559 # 560 # self.SetToolTipString(u'') 561 # 562 # if cursor_pos != self._old_cursor_pos: 563 # self._old_cursor_pos = cursor_pos 564 # (item, flags) = self.HitTest(cursor_pos) 565 # #if flags != wx.TREE_HITTEST_NOWHERE: 566 # if flags == wx.TREE_HITTEST_ONITEMLABEL: 567 # data = self.GetPyData(item) 568 # 569 # if not isinstance(data, gmEMRStructItems.cEncounter): 570 # return 571 # 572 # self.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 573 # data['started'].strftime('%x'), 574 # data['l10n_type'], 575 # data['started'].strftime('%H:%m'), 576 # data['last_affirmed'].strftime('%H:%m'), 577 # gmTools.coalesce(data['reason_for_encounter'], u''), 578 # gmTools.coalesce(data['assessment_of_encounter'], u'') 579 # )) 580 #--------------------------------------------------------
581 - def _on_tree_item_gettooltip(self, event):
582 583 item = event.GetItem() 584 585 if not item.IsOk(): 586 event.SetToolTip(u' ') 587 return 588 589 data = self.GetPyData(item) 590 591 if isinstance(data, gmEMRStructItems.cEncounter): 592 event.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 593 data['started'].strftime('%x'), 594 data['l10n_type'], 595 data['started'].strftime('%H:%M'), 596 data['last_affirmed'].strftime('%H:%M'), 597 gmTools.coalesce(data['reason_for_encounter'], u''), 598 gmTools.coalesce(data['assessment_of_encounter'], u'') 599 )) 600 601 elif isinstance(data, gmEMRStructItems.cEpisode): 602 tt = u'' 603 tt += gmTools.bool2subst ( 604 (data['diagnostic_certainty_classification'] is not None), 605 data.diagnostic_certainty_description + u'\n\n', 606 u'' 607 ) 608 tt += gmTools.bool2subst ( 609 data['episode_open'], 610 _('ongoing episode'), 611 _('closed episode'), 612 'error: episode state is None' 613 ) 614 if tt == u'': 615 tt = u' ' 616 event.SetToolTip(tt) 617 618 elif isinstance(data, gmEMRStructItems.cHealthIssue): 619 tt = u'' 620 tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), u'') 621 tt += gmTools.bool2subst ( 622 (data['diagnostic_certainty_classification'] is not None), 623 data.diagnostic_certainty_description + u'\n', 624 u'' 625 ) 626 tt += gmTools.bool2subst ( 627 (data['laterality'] not in [None, u'na']), 628 data.laterality_description + u'\n', 629 u'' 630 ) 631 # noted_at_age is too costly 632 tt += gmTools.bool2subst(data['is_active'], _('active') + u'\n', u'') 633 tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + u'\n', u'') 634 tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + u'\n', u'') 635 tt += gmTools.coalesce(data['grouping'], u'', _('Grouping: %s')) 636 if tt == u'': 637 tt = u' ' 638 event.SetToolTip(tt) 639 640 else: 641 event.SetToolTip(u' ')
642 #self.SetToolTipString(u'') 643 644 # doing this prevents the tooltip from showing at all 645 #event.Skip() 646 647 #widgetXY.GetToolTip().Enable(False) 648 # 649 #seems to work, supposing the tooltip is actually set for the widget, 650 #otherwise a test would be needed 651 #if widgetXY.GetToolTip(): 652 # widgetXY.GetToolTip().Enable(False) 653 #--------------------------------------------------------
654 - def _on_tree_item_right_clicked(self, event):
655 """Right button clicked: display the popup for the tree""" 656 657 node = event.GetItem() 658 self.SelectItem(node) 659 self.__curr_node_data = self.GetPyData(node) 660 self.__curr_node = node 661 662 pos = wx.DefaultPosition 663 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue): 664 self.__handle_issue_context(pos=pos) 665 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode): 666 self.__handle_episode_context(pos=pos) 667 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter): 668 self.__handle_encounter_context(pos=pos) 669 elif node == self.GetRootItem(): 670 self.__handle_root_context() 671 elif type(self.__curr_node_data) == type({}): 672 # ignore pseudo node "free-standing episodes" 673 pass 674 else: 675 print "error: unknown node type, no popup menu" 676 event.Skip()
677 #--------------------------------------------------------
678 - def OnCompareItems (self, node1=None, node2=None):
679 """Used in sorting items. 680 681 -1: 1 < 2 682 0: 1 = 2 683 1: 1 > 2 684 """ 685 # FIXME: implement sort modes, chron, reverse cron, by regex, etc 686 687 item1 = self.GetPyData(node1) 688 item2 = self.GetPyData(node2) 689 690 # encounters: reverse chron 691 if isinstance(item1, gmEMRStructItems.cEncounter): 692 if item1['started'] == item2['started']: 693 return 0 694 if item1['started'] > item2['started']: 695 return -1 696 return 1 697 698 # episodes: chron 699 if isinstance(item1, gmEMRStructItems.cEpisode): 700 start1 = item1.get_access_range()[0] 701 start2 = item2.get_access_range()[0] 702 if start1 == start2: 703 return 0 704 if start1 < start2: 705 return -1 706 return 1 707 708 # issues: alpha by grouping, no grouping at the bottom 709 if isinstance(item1, gmEMRStructItems.cHealthIssue): 710 711 # no grouping below grouping 712 if item1['grouping'] is None: 713 if item2['grouping'] is not None: 714 return 1 715 716 # grouping above no grouping 717 if item1['grouping'] is not None: 718 if item2['grouping'] is None: 719 return -1 720 721 # both no grouping: alpha on description 722 if (item1['grouping'] is None) and (item2['grouping'] is None): 723 if item1['description'].lower() < item2['description'].lower(): 724 return -1 725 if item1['description'].lower() > item2['description'].lower(): 726 return 1 727 return 0 728 729 # both with grouping: alpha on grouping, then alpha on description 730 if item1['grouping'] < item2['grouping']: 731 return -1 732 733 if item1['grouping'] > item2['grouping']: 734 return 1 735 736 if item1['description'].lower() < item2['description'].lower(): 737 return -1 738 739 if item1['description'].lower() > item2['description'].lower(): 740 return 1 741 742 return 0 743 744 # dummy health issue always on top 745 if isinstance(item1, type({})): 746 return -1 747 748 return 0
749 #================================================================ 750 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl 751
752 -class cScrolledEMRTreePnl(wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl):
753 """A scrollable panel holding an EMR tree. 754 755 Lacks a widget to display details for selected items. The 756 tree data will be refetched - if necessary - whenever 757 repopulate_ui() is called, e.g., when then patient is changed. 758 """
759 - def __init__(self, *args, **kwds):
761 # self.__register_interests() 762 #-------------------------------------------------------- 763 # def __register_interests(self): 764 # gmDispatcher.connect(signal= u'post_patient_selection', receiver=self.repopulate_ui) 765 #--------------------------------------------------------
766 - def repopulate_ui(self):
767 self._emr_tree.refresh() 768 return True
769 #============================================================ 770 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl 771
772 -class cSplittedEMRTreeBrowserPnl(wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl):
773 """A splitter window holding an EMR tree. 774 775 The left hand side displays a scrollable EMR tree while 776 on the right details for selected items are displayed. 777 778 Expects to be put into a Notebook. 779 """
780 - def __init__(self, *args, **kwds):
781 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds) 782 self._pnl_emr_tree._emr_tree.set_narrative_display(narrative_display = self._TCTRL_item_details) 783 self.__register_events()
784 #--------------------------------------------------------
785 - def __register_events(self):
786 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 787 return True
788 #--------------------------------------------------------
789 - def _on_post_patient_selection(self):
790 if self.GetParent().GetCurrentPage() == self: 791 self.repopulate_ui() 792 return True
793 #--------------------------------------------------------
794 - def repopulate_ui(self):
795 """Fills UI with data.""" 796 self._pnl_emr_tree.repopulate_ui() 797 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True) 798 return True
799 #================================================================
800 -class cEMRJournalPanel(wx.Panel):
801 - def __init__(self, *args, **kwargs):
802 wx.Panel.__init__(self, *args, **kwargs) 803 804 self.__do_layout() 805 self.__register_events()
806 #--------------------------------------------------------
807 - def __do_layout(self):
808 self.__journal = wx.TextCtrl ( 809 self, 810 -1, 811 _('No EMR data loaded.'), 812 style = wx.TE_MULTILINE | wx.TE_READONLY 813 ) 814 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)) 815 # arrange widgets 816 szr_outer = wx.BoxSizer(wx.VERTICAL) 817 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0) 818 # and do layout 819 self.SetAutoLayout(1) 820 self.SetSizer(szr_outer) 821 szr_outer.Fit(self) 822 szr_outer.SetSizeHints(self) 823 self.Layout()
824 #--------------------------------------------------------
825 - def __register_events(self):
826 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
827 #--------------------------------------------------------
828 - def _on_post_patient_selection(self):
829 """Expects to be in a Notebook.""" 830 if self.GetParent().GetCurrentPage() == self: 831 self.repopulate_ui() 832 return True
833 #-------------------------------------------------------- 834 # notebook plugin API 835 #--------------------------------------------------------
836 - def repopulate_ui(self):
837 txt = StringIO.StringIO() 838 exporter = gmPatientExporter.cEMRJournalExporter() 839 # FIXME: if journal is large this will error out, use generator/yield etc 840 # FIXME: turn into proper list 841 try: 842 exporter.export(txt) 843 self.__journal.SetValue(txt.getvalue()) 844 except ValueError: 845 _log.exception('cannot get EMR journal') 846 self.__journal.SetValue (_( 847 'An error occurred while retrieving the EMR\n' 848 'in journal form for the active patient.\n\n' 849 'Please check the log file for details.' 850 )) 851 txt.close() 852 self.__journal.ShowPosition(self.__journal.GetLastPosition()) 853 return True
854 #================================================================ 855 # MAIN 856 #---------------------------------------------------------------- 857 if __name__ == '__main__': 858 859 _log.info("starting emr browser...") 860 861 try: 862 # obtain patient 863 patient = gmPerson.ask_for_patient() 864 if patient is None: 865 print "No patient. Exiting gracefully..." 866 sys.exit(0) 867 gmPatSearchWidgets.set_active_patient(patient = patient) 868 869 # display standalone browser 870 application = wx.PyWidgetTester(size=(800,600)) 871 emr_browser = cEMRBrowserPanel(application.frame, -1) 872 emr_browser.refresh_tree() 873 874 application.frame.Show(True) 875 application.MainLoop() 876 877 # clean up 878 if patient is not None: 879 try: 880 patient.cleanup() 881 except: 882 print "error cleaning up patient" 883 except StandardError: 884 _log.exception("unhandled exception caught !") 885 # but re-raise them 886 raise 887 888 _log.info("closing emr browser...") 889 890 #================================================================ 891