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 
 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 procedures'))) 268 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_procedures) 269 270 menu_id = wx.NewId() 271 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage hospitalizations'))) 272 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_hospital_stays) 273 274 menu_id = wx.NewId() 275 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage occupation'))) 276 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_occupation) 277 278 self.__root_context_popup.AppendSeparator() 279 280 # expand tree 281 expand_menu = wx.Menu() 282 self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu) 283 284 menu_id = wx.NewId() 285 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level'))) 286 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level) 287 288 menu_id = wx.NewId() 289 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level'))) 290 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level) 291 292 menu_id = wx.NewId() 293 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level'))) 294 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
295 #--------------------------------------------------------
296 - def __handle_root_context(self, pos=wx.DefaultPosition):
297 self.PopupMenu(self.__root_context_popup, pos)
298 #--------------------------------------------------------
299 - def __handle_issue_context(self, pos=wx.DefaultPosition):
300 # self.__issue_context_popup.SetTitle(_('Episode %s') % episode['description']) 301 self.PopupMenu(self.__issue_context_popup, pos)
302 #--------------------------------------------------------
303 - def __handle_episode_context(self, pos=wx.DefaultPosition):
304 self.__epi_context_popup.SetTitle(_('Episode %s') % self.__curr_node_data['description']) 305 self.PopupMenu(self.__epi_context_popup, pos)
306 #--------------------------------------------------------
307 - def __handle_encounter_context(self, pos=wx.DefaultPosition):
308 self.PopupMenu(self.__enc_context_popup, pos)
309 #-------------------------------------------------------- 310 # episode level 311 #--------------------------------------------------------
312 - def __move_encounters(self, event):
313 episode = self.GetPyData(self.__curr_node) 314 315 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 316 parent = self, 317 episodes = [episode['pk_episode']], 318 move_all = True 319 )
320 #--------------------------------------------------------
321 - def __edit_episode(self, event):
322 gmEMRStructWidgets.edit_episode(parent = self, episode = self.__curr_node_data)
323 #--------------------------------------------------------
324 - def __promote_episode_to_issue(self, evt):
325 pat = gmPerson.gmCurrentPatient() 326 gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = pat.get_emr())
327 #--------------------------------------------------------
328 - def __delete_episode(self, event):
329 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 330 parent = self, 331 id = -1, 332 caption = _('Deleting episode'), 333 button_defs = [ 334 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')}, 335 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')} 336 ], 337 question = _( 338 'Are you sure you want to delete this episode ?\n' 339 '\n' 340 ' "%s"\n' 341 ) % self.__curr_node_data['description'] 342 ) 343 result = dlg.ShowModal() 344 if result != wx.ID_YES: 345 return 346 347 try: 348 gmEMRStructItems.delete_episode(episode = self.__curr_node_data) 349 except gmExceptions.DatabaseObjectInUseError: 350 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.')) 351 return
352 #-------------------------------------------------------- 353 # encounter level 354 #--------------------------------------------------------
355 - def __move_progress_notes(self, evt):
356 encounter = self.GetPyData(self.__curr_node) 357 node_parent = self.GetItemParent(self.__curr_node) 358 episode = self.GetPyData(node_parent) 359 360 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 361 parent = self, 362 encounters = [encounter['pk_encounter']], 363 episodes = [episode['pk_episode']] 364 )
365 #--------------------------------------------------------
366 - def __edit_progress_notes(self, event):
367 encounter = self.GetPyData(self.__curr_node) 368 node_parent = self.GetItemParent(self.__curr_node) 369 episode = self.GetPyData(node_parent) 370 371 gmNarrativeWidgets.manage_progress_notes ( 372 parent = self, 373 encounters = [encounter['pk_encounter']], 374 episodes = [episode['pk_episode']] 375 )
376 #--------------------------------------------------------
377 - def __edit_encounter_details(self, event):
378 node_data = self.GetPyData(self.__curr_node) 379 dlg = gmEMRStructWidgets.cEncounterEditAreaDlg(parent=self, encounter=node_data) 380 dlg.ShowModal() 381 dlg.Destroy() 382 self.__populate_tree()
383 #-------------------------------------------------------- 401 #-------------------------------------------------------- 402 # issue level 403 #--------------------------------------------------------
404 - def __edit_issue(self, event):
405 gmEMRStructWidgets.edit_health_issue(parent = self, issue = self.__curr_node_data)
406 #--------------------------------------------------------
407 - def __delete_issue(self, event):
408 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 409 parent = self, 410 id = -1, 411 caption = _('Deleting health issue'), 412 button_defs = [ 413 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')}, 414 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')} 415 ], 416 question = _( 417 'Are you sure you want to delete this health issue ?\n' 418 '\n' 419 ' "%s"\n' 420 ) % self.__curr_node_data['description'] 421 ) 422 result = dlg.ShowModal() 423 if result != wx.ID_YES: 424 dlg.Destroy() 425 return 426 427 dlg.Destroy() 428 429 try: 430 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data) 431 except gmExceptions.DatabaseObjectInUseError: 432 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
433 #--------------------------------------------------------
435 436 if not self.__curr_node.IsOk(): 437 return 438 439 self.Expand(self.__curr_node) 440 441 epi, epi_cookie = self.GetFirstChild(self.__curr_node) 442 while epi.IsOk(): 443 self.Expand(epi) 444 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
445 #-------------------------------------------------------- 446 # EMR level 447 #--------------------------------------------------------
448 - def __create_issue(self, event):
449 gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
450 #--------------------------------------------------------
451 - def __document_allergy(self, event):
452 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1) 453 # FIXME: use signal and use node level update 454 if dlg.ShowModal() == wx.ID_OK: 455 self.__populate_tree() 456 dlg.Destroy() 457 return
458 #--------------------------------------------------------
459 - def __manage_procedures(self, event):
461 #--------------------------------------------------------
462 - def __manage_hospital_stays(self, event):
464 #--------------------------------------------------------
465 - def __manage_occupation(self, event):
467 #--------------------------------------------------------
468 - def __expand_to_issue_level(self, evt):
469 470 root_item = self.GetRootItem() 471 472 if not root_item.IsOk(): 473 return 474 475 self.Expand(root_item) 476 477 # collapse episodes and issues 478 issue, issue_cookie = self.GetFirstChild(root_item) 479 while issue.IsOk(): 480 self.Collapse(issue) 481 epi, epi_cookie = self.GetFirstChild(issue) 482 while epi.IsOk(): 483 self.Collapse(epi) 484 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 485 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
486 #--------------------------------------------------------
487 - def __expand_to_episode_level(self, evt):
488 489 root_item = self.GetRootItem() 490 491 if not root_item.IsOk(): 492 return 493 494 self.Expand(root_item) 495 496 # collapse episodes, expand issues 497 issue, issue_cookie = self.GetFirstChild(root_item) 498 while issue.IsOk(): 499 self.Expand(issue) 500 epi, epi_cookie = self.GetFirstChild(issue) 501 while epi.IsOk(): 502 self.Collapse(epi) 503 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 504 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
505 #--------------------------------------------------------
506 - def __expand_to_encounter_level(self, evt):
507 508 root_item = self.GetRootItem() 509 510 if not root_item.IsOk(): 511 return 512 513 self.Expand(root_item) 514 515 # collapse episodes, expand issues 516 issue, issue_cookie = self.GetFirstChild(root_item) 517 while issue.IsOk(): 518 self.Expand(issue) 519 epi, epi_cookie = self.GetFirstChild(issue) 520 while epi.IsOk(): 521 self.Expand(epi) 522 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 523 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
524 #--------------------------------------------------------
525 - def __export_encounter_for_medistar(self, evt):
526 gmNarrativeWidgets.export_narrative_for_medistar_import ( 527 parent = self, 528 soap_cats = u'soap', 529 encounter = self.__curr_node_data 530 )
531 #-------------------------------------------------------- 532 # event handlers 533 #--------------------------------------------------------
534 - def _on_narrative_mod_db(self, *args, **kwargs):
535 wx.CallAfter(self.__update_text_for_selected_node)
536 #--------------------------------------------------------
537 - def _on_episode_mod_db(self, *args, **kwargs):
538 wx.CallAfter(self.__populate_tree)
539 #--------------------------------------------------------
540 - def _on_issue_mod_db(self, *args, **kwargs):
541 wx.CallAfter(self.__populate_tree)
542 #--------------------------------------------------------
543 - def _on_tree_item_selected(self, event):
544 sel_item = event.GetItem() 545 self.__curr_node = sel_item 546 self.__update_text_for_selected_node() 547 return True
548 # #-------------------------------------------------------- 549 # def _on_mouse_motion(self, event): 550 # 551 # cursor_pos = (event.GetX(), event.GetY()) 552 # 553 # self.SetToolTipString(u'') 554 # 555 # if cursor_pos != self._old_cursor_pos: 556 # self._old_cursor_pos = cursor_pos 557 # (item, flags) = self.HitTest(cursor_pos) 558 # #if flags != wx.TREE_HITTEST_NOWHERE: 559 # if flags == wx.TREE_HITTEST_ONITEMLABEL: 560 # data = self.GetPyData(item) 561 # 562 # if not isinstance(data, gmEMRStructItems.cEncounter): 563 # return 564 # 565 # self.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 566 # data['started'].strftime('%x'), 567 # data['l10n_type'], 568 # data['started'].strftime('%H:%m'), 569 # data['last_affirmed'].strftime('%H:%m'), 570 # gmTools.coalesce(data['reason_for_encounter'], u''), 571 # gmTools.coalesce(data['assessment_of_encounter'], u'') 572 # )) 573 #--------------------------------------------------------
574 - def _on_tree_item_gettooltip(self, event):
575 576 item = event.GetItem() 577 578 if not item.IsOk(): 579 event.SetToolTip(u' ') 580 return 581 582 data = self.GetPyData(item) 583 584 if isinstance(data, gmEMRStructItems.cEncounter): 585 event.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 586 data['started'].strftime('%x'), 587 data['l10n_type'], 588 data['started'].strftime('%H:%M'), 589 data['last_affirmed'].strftime('%H:%M'), 590 gmTools.coalesce(data['reason_for_encounter'], u''), 591 gmTools.coalesce(data['assessment_of_encounter'], u'') 592 )) 593 594 elif isinstance(data, gmEMRStructItems.cEpisode): 595 tt = u'' 596 tt += gmTools.bool2subst ( 597 (data['diagnostic_certainty_classification'] is not None), 598 data.diagnostic_certainty_description + u'\n\n', 599 u'' 600 ) 601 tt += gmTools.bool2subst ( 602 data['episode_open'], 603 _('ongoing episode'), 604 _('closed episode'), 605 'error: episode state is None' 606 ) 607 if tt == u'': 608 tt = u' ' 609 event.SetToolTip(tt) 610 611 elif isinstance(data, gmEMRStructItems.cHealthIssue): 612 tt = u'' 613 tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), u'') 614 tt += gmTools.bool2subst ( 615 (data['diagnostic_certainty_classification'] is not None), 616 data.diagnostic_certainty_description + u'\n', 617 u'' 618 ) 619 tt += gmTools.bool2subst ( 620 (data['laterality'] not in [None, u'na']), 621 data.laterality_description + u'\n', 622 u'' 623 ) 624 # noted_at_age is too costly 625 tt += gmTools.bool2subst(data['is_active'], _('active') + u'\n', u'') 626 tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + u'\n', u'') 627 tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + u'\n', u'') 628 tt += gmTools.coalesce(data['grouping'], u'', _('Grouping: %s')) 629 if tt == u'': 630 tt = u' ' 631 event.SetToolTip(tt) 632 633 else: 634 event.SetToolTip(u' ')
635 #self.SetToolTipString(u'') 636 637 # doing this prevents the tooltip from showing at all 638 #event.Skip() 639 640 #widgetXY.GetToolTip().Enable(False) 641 # 642 #seems to work, supposing the tooltip is actually set for the widget, 643 #otherwise a test would be needed 644 #if widgetXY.GetToolTip(): 645 # widgetXY.GetToolTip().Enable(False) 646 #--------------------------------------------------------
647 - def _on_tree_item_right_clicked(self, event):
648 """Right button clicked: display the popup for the tree""" 649 650 node = event.GetItem() 651 self.SelectItem(node) 652 self.__curr_node_data = self.GetPyData(node) 653 self.__curr_node = node 654 655 pos = wx.DefaultPosition 656 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue): 657 self.__handle_issue_context(pos=pos) 658 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode): 659 self.__handle_episode_context(pos=pos) 660 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter): 661 self.__handle_encounter_context(pos=pos) 662 elif node == self.GetRootItem(): 663 self.__handle_root_context() 664 elif type(self.__curr_node_data) == type({}): 665 # ignore pseudo node "free-standing episodes" 666 pass 667 else: 668 print "error: unknown node type, no popup menu" 669 event.Skip()
670 #--------------------------------------------------------
671 - def OnCompareItems (self, node1=None, node2=None):
672 """Used in sorting items. 673 674 -1: 1 < 2 675 0: 1 = 2 676 1: 1 > 2 677 """ 678 # FIXME: implement sort modes, chron, reverse cron, by regex, etc 679 680 item1 = self.GetPyData(node1) 681 item2 = self.GetPyData(node2) 682 683 # encounters: reverse chron 684 if isinstance(item1, gmEMRStructItems.cEncounter): 685 if item1['started'] == item2['started']: 686 return 0 687 if item1['started'] > item2['started']: 688 return -1 689 return 1 690 691 # episodes: chron 692 if isinstance(item1, gmEMRStructItems.cEpisode): 693 start1 = item1.get_access_range()[0] 694 start2 = item2.get_access_range()[0] 695 if start1 == start2: 696 return 0 697 if start1 < start2: 698 return -1 699 return 1 700 701 # issues: alpha by grouping, no grouping at the bottom 702 if isinstance(item1, gmEMRStructItems.cHealthIssue): 703 704 # no grouping below grouping 705 if item1['grouping'] is None: 706 if item2['grouping'] is not None: 707 return 1 708 709 # grouping above no grouping 710 if item1['grouping'] is not None: 711 if item2['grouping'] is None: 712 return -1 713 714 # both no grouping: alpha on description 715 if (item1['grouping'] is None) and (item2['grouping'] is None): 716 if item1['description'].lower() < item2['description'].lower(): 717 return -1 718 if item1['description'].lower() > item2['description'].lower(): 719 return 1 720 return 0 721 722 # both with grouping: alpha on grouping, then alpha on description 723 if item1['grouping'] < item2['grouping']: 724 return -1 725 726 if item1['grouping'] > item2['grouping']: 727 return 1 728 729 if item1['description'].lower() < item2['description'].lower(): 730 return -1 731 732 if item1['description'].lower() > item2['description'].lower(): 733 return 1 734 735 return 0 736 737 # dummy health issue always on top 738 if isinstance(item1, type({})): 739 return -1 740 741 return 0
742 #================================================================ 743 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl 744
745 -class cScrolledEMRTreePnl(wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl):
746 """A scrollable panel holding an EMR tree. 747 748 Lacks a widget to display details for selected items. The 749 tree data will be refetched - if necessary - whenever 750 repopulate_ui() is called, e.g., when then patient is changed. 751 """
752 - def __init__(self, *args, **kwds):
754 # self.__register_interests() 755 #-------------------------------------------------------- 756 # def __register_interests(self): 757 # gmDispatcher.connect(signal= u'post_patient_selection', receiver=self.repopulate_ui) 758 #--------------------------------------------------------
759 - def repopulate_ui(self):
760 self._emr_tree.refresh() 761 return True
762 #============================================================ 763 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl 764
765 -class cSplittedEMRTreeBrowserPnl(wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl):
766 """A splitter window holding an EMR tree. 767 768 The left hand side displays a scrollable EMR tree while 769 on the right details for selected items are displayed. 770 771 Expects to be put into a Notebook. 772 """
773 - def __init__(self, *args, **kwds):
774 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds) 775 self._pnl_emr_tree._emr_tree.set_narrative_display(narrative_display = self._TCTRL_item_details) 776 self.__register_events()
777 #--------------------------------------------------------
778 - def __register_events(self):
779 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 780 return True
781 #--------------------------------------------------------
782 - def _on_post_patient_selection(self):
783 if self.GetParent().GetCurrentPage() == self: 784 self.repopulate_ui() 785 return True
786 #--------------------------------------------------------
787 - def repopulate_ui(self):
788 """Fills UI with data.""" 789 self._pnl_emr_tree.repopulate_ui() 790 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True) 791 return True
792 #================================================================
793 -class cEMRJournalPanel(wx.Panel):
794 - def __init__(self, *args, **kwargs):
795 wx.Panel.__init__(self, *args, **kwargs) 796 797 self.__do_layout() 798 self.__register_events()
799 #--------------------------------------------------------
800 - def __do_layout(self):
801 self.__journal = wx.TextCtrl ( 802 self, 803 -1, 804 _('No EMR data loaded.'), 805 style = wx.TE_MULTILINE | wx.TE_READONLY 806 ) 807 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)) 808 # arrange widgets 809 szr_outer = wx.BoxSizer(wx.VERTICAL) 810 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0) 811 # and do layout 812 self.SetAutoLayout(1) 813 self.SetSizer(szr_outer) 814 szr_outer.Fit(self) 815 szr_outer.SetSizeHints(self) 816 self.Layout()
817 #--------------------------------------------------------
818 - def __register_events(self):
819 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
820 #--------------------------------------------------------
821 - def _on_post_patient_selection(self):
822 """Expects to be in a Notebook.""" 823 if self.GetParent().GetCurrentPage() == self: 824 self.repopulate_ui() 825 return True
826 #-------------------------------------------------------- 827 # notebook plugin API 828 #--------------------------------------------------------
829 - def repopulate_ui(self):
830 txt = StringIO.StringIO() 831 exporter = gmPatientExporter.cEMRJournalExporter() 832 # FIXME: if journal is large this will error out, use generator/yield etc 833 # FIXME: turn into proper list 834 try: 835 exporter.export(txt) 836 self.__journal.SetValue(txt.getvalue()) 837 except ValueError: 838 _log.exception('cannot get EMR journal') 839 self.__journal.SetValue (_( 840 'An error occurred while retrieving the EMR\n' 841 'in journal form for the active patient.\n\n' 842 'Please check the log file for details.' 843 )) 844 txt.close() 845 self.__journal.ShowPosition(self.__journal.GetLastPosition()) 846 return True
847 #================================================================ 848 # MAIN 849 #---------------------------------------------------------------- 850 if __name__ == '__main__': 851 852 _log.info("starting emr browser...") 853 854 try: 855 # obtain patient 856 patient = gmPerson.ask_for_patient() 857 if patient is None: 858 print "No patient. Exiting gracefully..." 859 sys.exit(0) 860 gmPatSearchWidgets.set_active_patient(patient = patient) 861 862 # display standalone browser 863 application = wx.PyWidgetTester(size=(800,600)) 864 emr_browser = cEMRBrowserPanel(application.frame, -1) 865 emr_browser.refresh_tree() 866 867 application.frame.Show(True) 868 application.MainLoop() 869 870 # clean up 871 if patient is not None: 872 try: 873 patient.cleanup() 874 except: 875 print "error cleaning up patient" 876 except StandardError: 877 _log.exception("unhandled exception caught !") 878 # but re-raise them 879 raise 880 881 _log.info("closing emr browser...") 882 883 #================================================================ 884