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, gmPersonSearch 
 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 96 self.__pat = gmPerson.gmCurrentPatient() 97 self.__curr_node = None 98 self.__exporter = gmPatientExporter.cEmrExport(patient = self.__pat) 99 100 self._old_cursor_pos = None 101 102 self.__make_popup_menus() 103 self.__register_events()
104 #-------------------------------------------------------- 105 # external API 106 #--------------------------------------------------------
107 - def refresh(self):
108 if not self.__pat.connected: 109 gmDispatcher.send(signal='statustext', msg=_('Cannot load clinical narrative. No active patient.'),) 110 return False 111 112 if not self.__populate_tree(): 113 return False 114 115 return True
116 #--------------------------------------------------------
117 - def set_narrative_display(self, narrative_display=None):
118 self.__narr_display = narrative_display
119 #--------------------------------------------------------
120 - def set_image_display(self, image_display=None):
121 self.__img_display = image_display
122 #-------------------------------------------------------- 123 # internal helpers 124 #--------------------------------------------------------
125 - def __register_events(self):
126 """Configures enabled event signals.""" 127 wx.EVT_TREE_SEL_CHANGED (self, self.GetId(), self._on_tree_item_selected) 128 wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self._on_tree_item_right_clicked) 129 130 # handle tooltips 131 # wx.EVT_MOTION(self, self._on_mouse_motion) 132 wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip) 133 134 gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db) 135 gmDispatcher.connect(signal = 'episode_mod_db', receiver = self._on_episode_mod_db) 136 gmDispatcher.connect(signal = 'health_issue_mod_db', receiver = self._on_issue_mod_db)
137 #--------------------------------------------------------
138 - def __populate_tree(self):
139 """Updates EMR browser data.""" 140 # FIXME: auto select the previously self.__curr_node if not None 141 # FIXME: error handling 142 143 wx.BeginBusyCursor() 144 145 # self.snapshot_expansion() 146 147 # init new tree 148 self.DeleteAllItems() 149 root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name()) 150 self.SetPyData(root_item, None) 151 self.SetItemHasChildren(root_item, True) 152 self.__root_tooltip = self.__pat['description_gender'] + u'\n' 153 if self.__pat['deceased'] is None: 154 self.__root_tooltip += u' %s %s (%s)\n\n' % ( 155 gmPerson.map_gender2symbol[self.__pat['gender']], 156 self.__pat.get_formatted_dob(format = '%d %b %Y', encoding = gmI18N.get_encoding()), 157 self.__pat['medical_age'] 158 ) 159 else: 160 template = u' %s %s - %s (%s)\n\n' 161 self.__root_tooltip += template % ( 162 gmPerson.map_gender2symbol[self.__pat['gender']], 163 self.__pat.get_formatted_dob(format = '%d.%b %Y', encoding = gmI18N.get_encoding()), 164 self.__pat['deceased'].strftime('%d.%b %Y').decode(gmI18N.get_encoding()), 165 self.__pat['medical_age'] 166 ) 167 self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], u'', u'%s\n\n') 168 doc = self.__pat.primary_provider 169 if doc is not None: 170 self.__root_tooltip += u'%s:\n' % _('Primary provider in this praxis') 171 self.__root_tooltip += u' %s %s %s (%s)%s\n\n' % ( 172 gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])), 173 doc['firstnames'], 174 doc['lastnames'], 175 doc['short_alias'], 176 gmTools.bool2subst(doc['is_active'], u'', u' [%s]' % _('inactive')) 177 ) 178 if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)): 179 self.__root_tooltip += _('In case of emergency contact:') + u'\n' 180 if self.__pat['emergency_contact'] is not None: 181 self.__root_tooltip += gmTools.wrap ( 182 text = u'%s\n' % self.__pat['emergency_contact'], 183 width = 60, 184 initial_indent = u' ', 185 subsequent_indent = u' ' 186 ) 187 if self.__pat['pk_emergency_contact'] is not None: 188 contact = self.__pat.emergency_contact_in_database 189 self.__root_tooltip += u' %s\n' % contact['description_gender'] 190 self.__root_tooltip = self.__root_tooltip.strip('\n') 191 if self.__root_tooltip == u'': 192 self.__root_tooltip = u' ' 193 194 # have the tree filled by the exporter 195 self.__exporter.get_historical_tree(self) 196 self.__curr_node = root_item 197 198 self.SelectItem(root_item) 199 self.Expand(root_item) 200 self.__update_text_for_selected_node() 201 202 # self.restore_expansion() 203 204 wx.EndBusyCursor() 205 return True
206 #--------------------------------------------------------
208 """Displays information for the selected tree node.""" 209 210 if self.__narr_display is None: 211 self.__img_display.clear() 212 return 213 214 if self.__curr_node is None: 215 self.__img_display.clear() 216 return 217 218 node_data = self.GetPyData(self.__curr_node) 219 doc_folder = self.__pat.get_document_folder() 220 221 # update displayed details 222 if isinstance(node_data, (gmEMRStructItems.cHealthIssue, types.DictType)): 223 # FIXME: turn into real dummy issue 224 if node_data['pk_health_issue'] is None: 225 txt = _('Pool of unassociated episodes:\n\n "%s"') % node_data['description'] 226 self.__img_display.clear() 227 else: 228 txt = node_data.format(left_margin=1, patient = self.__pat) 229 self.__img_display.refresh ( 230 document_folder = doc_folder, 231 episodes = [ epi['pk_episode'] for epi in node_data.episodes ] 232 ) 233 234 elif isinstance(node_data, gmEMRStructItems.cEpisode): 235 txt = node_data.format(left_margin = 1, patient = self.__pat) 236 self.__img_display.refresh ( 237 document_folder = doc_folder, 238 episodes = [node_data['pk_episode']] 239 ) 240 241 elif isinstance(node_data, gmEMRStructItems.cEncounter): 242 epi = self.GetPyData(self.GetItemParent(self.__curr_node)) 243 txt = node_data.format ( 244 episodes = [epi['pk_episode']], 245 with_soap = True, 246 left_margin = 1, 247 patient = self.__pat 248 ) 249 self.__img_display.refresh ( 250 document_folder = doc_folder, 251 episodes = [epi['pk_episode']], 252 encounter = node_data['pk_encounter'] 253 ) 254 255 else: 256 emr = self.__pat.get_emr() 257 txt = emr.format_summary(dob = self.__pat['dob']) 258 self.__img_display.clear() 259 260 self.__narr_display.Clear() 261 self.__narr_display.WriteText(txt)
262 #--------------------------------------------------------
263 - def __make_popup_menus(self):
264 265 # - episodes 266 self.__epi_context_popup = wx.Menu(title = _('Episode Menu')) 267 268 menu_id = wx.NewId() 269 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Edit details'))) 270 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__edit_episode) 271 272 menu_id = wx.NewId() 273 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Delete'))) 274 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__delete_episode) 275 276 menu_id = wx.NewId() 277 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Promote'))) 278 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__promote_episode_to_issue) 279 280 menu_id = wx.NewId() 281 self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Move encounters'))) 282 wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__move_encounters) 283 284 # - encounters 285 self.__enc_context_popup = wx.Menu(title = _('Encounter Menu')) 286 # - move data 287 menu_id = wx.NewId() 288 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Move data to another episode'))) 289 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__relink_encounter_data2episode) 290 # - edit encounter details 291 menu_id = wx.NewId() 292 self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Edit details'))) 293 wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__edit_encounter_details) 294 295 item = self.__enc_context_popup.Append(-1, _('Edit progress notes')) 296 self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item) 297 298 item = self.__enc_context_popup.Append(-1, _('Move progress notes')) 299 self.Bind(wx.EVT_MENU, self.__move_progress_notes, item) 300 301 item = self.__enc_context_popup.Append(-1, _('Export for Medistar')) 302 self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item) 303 304 # - health issues 305 self.__issue_context_popup = wx.Menu(title = _('Health Issue Menu')) 306 307 menu_id = wx.NewId() 308 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Edit details'))) 309 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__edit_issue) 310 311 menu_id = wx.NewId() 312 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Delete'))) 313 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__delete_issue) 314 315 self.__issue_context_popup.AppendSeparator() 316 317 menu_id = wx.NewId() 318 self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Open to encounter level'))) 319 wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__expand_issue_to_encounter_level) 320 # print " attach issue to another patient" 321 # print " move all episodes to another issue" 322 323 # - root node 324 self.__root_context_popup = wx.Menu(title = _('EMR Menu')) 325 326 menu_id = wx.NewId() 327 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Create health issue'))) 328 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__create_issue) 329 330 menu_id = wx.NewId() 331 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage allergies'))) 332 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__document_allergy) 333 334 menu_id = wx.NewId() 335 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage vaccinations'))) 336 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_vaccinations) 337 338 menu_id = wx.NewId() 339 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage procedures'))) 340 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_procedures) 341 342 menu_id = wx.NewId() 343 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage hospitalizations'))) 344 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_hospital_stays) 345 346 menu_id = wx.NewId() 347 self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage occupation'))) 348 wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_occupation) 349 350 self.__root_context_popup.AppendSeparator() 351 352 # expand tree 353 expand_menu = wx.Menu() 354 self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu) 355 356 menu_id = wx.NewId() 357 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level'))) 358 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level) 359 360 menu_id = wx.NewId() 361 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level'))) 362 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level) 363 364 menu_id = wx.NewId() 365 expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level'))) 366 wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
367 #--------------------------------------------------------
368 - def __handle_root_context(self, pos=wx.DefaultPosition):
369 self.PopupMenu(self.__root_context_popup, pos)
370 #--------------------------------------------------------
371 - def __handle_issue_context(self, pos=wx.DefaultPosition):
372 # self.__issue_context_popup.SetTitle(_('Episode %s') % episode['description']) 373 self.PopupMenu(self.__issue_context_popup, pos)
374 #--------------------------------------------------------
375 - def __handle_episode_context(self, pos=wx.DefaultPosition):
376 self.__epi_context_popup.SetTitle(_('Episode %s') % self.__curr_node_data['description']) 377 self.PopupMenu(self.__epi_context_popup, pos)
378 #--------------------------------------------------------
379 - def __handle_encounter_context(self, pos=wx.DefaultPosition):
380 self.PopupMenu(self.__enc_context_popup, pos)
381 #-------------------------------------------------------- 382 # episode level 383 #--------------------------------------------------------
384 - def __move_encounters(self, event):
385 episode = self.GetPyData(self.__curr_node) 386 387 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 388 parent = self, 389 episodes = [episode['pk_episode']], 390 move_all = True 391 )
392 #--------------------------------------------------------
393 - def __edit_episode(self, event):
394 gmEMRStructWidgets.edit_episode(parent = self, episode = self.__curr_node_data)
395 #--------------------------------------------------------
396 - def __promote_episode_to_issue(self, evt):
397 pat = gmPerson.gmCurrentPatient() 398 gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = pat.get_emr())
399 #--------------------------------------------------------
400 - def __delete_episode(self, event):
401 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 402 parent = self, 403 id = -1, 404 caption = _('Deleting episode'), 405 button_defs = [ 406 {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')}, 407 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')} 408 ], 409 question = _( 410 'Are you sure you want to delete this episode ?\n' 411 '\n' 412 ' "%s"\n' 413 ) % self.__curr_node_data['description'] 414 ) 415 result = dlg.ShowModal() 416 if result != wx.ID_YES: 417 return 418 419 try: 420 gmEMRStructItems.delete_episode(episode = self.__curr_node_data) 421 except gmExceptions.DatabaseObjectInUseError: 422 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.')) 423 return
424 #-------------------------------------------------------- 425 # encounter level 426 #--------------------------------------------------------
427 - def __move_progress_notes(self, evt):
428 encounter = self.GetPyData(self.__curr_node) 429 node_parent = self.GetItemParent(self.__curr_node) 430 episode = self.GetPyData(node_parent) 431 432 gmNarrativeWidgets.move_progress_notes_to_another_encounter ( 433 parent = self, 434 encounters = [encounter['pk_encounter']], 435 episodes = [episode['pk_episode']] 436 )
437 #--------------------------------------------------------
438 - def __edit_progress_notes(self, event):
439 encounter = self.GetPyData(self.__curr_node) 440 node_parent = self.GetItemParent(self.__curr_node) 441 episode = self.GetPyData(node_parent) 442 443 gmNarrativeWidgets.manage_progress_notes ( 444 parent = self, 445 encounters = [encounter['pk_encounter']], 446 episodes = [episode['pk_episode']] 447 )
448 #--------------------------------------------------------
449 - def __edit_encounter_details(self, event):
450 node_data = self.GetPyData(self.__curr_node) 451 dlg = gmEMRStructWidgets.cEncounterEditAreaDlg(parent=self, encounter=node_data) 452 dlg.ShowModal() 453 dlg.Destroy() 454 self.__populate_tree()
455 #-------------------------------------------------------- 473 #-------------------------------------------------------- 474 # issue level 475 #--------------------------------------------------------
476 - def __edit_issue(self, event):
477 gmEMRStructWidgets.edit_health_issue(parent = self, issue = self.__curr_node_data)
478 #--------------------------------------------------------
479 - def __delete_issue(self, event):
480 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 481 parent = self, 482 id = -1, 483 caption = _('Deleting health issue'), 484 button_defs = [ 485 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')}, 486 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')} 487 ], 488 question = _( 489 'Are you sure you want to delete this health issue ?\n' 490 '\n' 491 ' "%s"\n' 492 ) % self.__curr_node_data['description'] 493 ) 494 result = dlg.ShowModal() 495 if result != wx.ID_YES: 496 dlg.Destroy() 497 return 498 499 dlg.Destroy() 500 501 try: 502 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data) 503 except gmExceptions.DatabaseObjectInUseError: 504 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
505 #--------------------------------------------------------
507 508 if not self.__curr_node.IsOk(): 509 return 510 511 self.Expand(self.__curr_node) 512 513 epi, epi_cookie = self.GetFirstChild(self.__curr_node) 514 while epi.IsOk(): 515 self.Expand(epi) 516 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
517 #-------------------------------------------------------- 518 # EMR level 519 #--------------------------------------------------------
520 - def __create_issue(self, event):
521 gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
522 #--------------------------------------------------------
523 - def __document_allergy(self, event):
524 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1) 525 # FIXME: use signal and use node level update 526 if dlg.ShowModal() == wx.ID_OK: 527 self.__populate_tree() 528 dlg.Destroy() 529 return
530 #--------------------------------------------------------
531 - def __manage_procedures(self, event):
533 #--------------------------------------------------------
534 - def __manage_hospital_stays(self, event):
536 #--------------------------------------------------------
537 - def __manage_occupation(self, event):
539 #--------------------------------------------------------
540 - def __manage_vaccinations(self, event):
542 #--------------------------------------------------------
543 - def __expand_to_issue_level(self, evt):
544 545 root_item = self.GetRootItem() 546 547 if not root_item.IsOk(): 548 return 549 550 self.Expand(root_item) 551 552 # collapse episodes and issues 553 issue, issue_cookie = self.GetFirstChild(root_item) 554 while issue.IsOk(): 555 self.Collapse(issue) 556 epi, epi_cookie = self.GetFirstChild(issue) 557 while epi.IsOk(): 558 self.Collapse(epi) 559 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 560 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
561 #--------------------------------------------------------
562 - def __expand_to_episode_level(self, evt):
563 564 root_item = self.GetRootItem() 565 566 if not root_item.IsOk(): 567 return 568 569 self.Expand(root_item) 570 571 # collapse episodes, expand issues 572 issue, issue_cookie = self.GetFirstChild(root_item) 573 while issue.IsOk(): 574 self.Expand(issue) 575 epi, epi_cookie = self.GetFirstChild(issue) 576 while epi.IsOk(): 577 self.Collapse(epi) 578 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 579 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
580 #--------------------------------------------------------
581 - def __expand_to_encounter_level(self, evt):
582 583 root_item = self.GetRootItem() 584 585 if not root_item.IsOk(): 586 return 587 588 self.Expand(root_item) 589 590 # collapse episodes, expand issues 591 issue, issue_cookie = self.GetFirstChild(root_item) 592 while issue.IsOk(): 593 self.Expand(issue) 594 epi, epi_cookie = self.GetFirstChild(issue) 595 while epi.IsOk(): 596 self.Expand(epi) 597 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 598 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
599 #--------------------------------------------------------
600 - def __export_encounter_for_medistar(self, evt):
601 gmNarrativeWidgets.export_narrative_for_medistar_import ( 602 parent = self, 603 soap_cats = u'soap', 604 encounter = self.__curr_node_data 605 )
606 #-------------------------------------------------------- 607 # event handlers 608 #--------------------------------------------------------
609 - def _on_narrative_mod_db(self, *args, **kwargs):
610 wx.CallAfter(self.__update_text_for_selected_node)
611 #--------------------------------------------------------
612 - def _on_episode_mod_db(self, *args, **kwargs):
613 wx.CallAfter(self.__populate_tree)
614 #--------------------------------------------------------
615 - def _on_issue_mod_db(self, *args, **kwargs):
616 wx.CallAfter(self.__populate_tree)
617 #--------------------------------------------------------
618 - def _on_tree_item_selected(self, event):
619 sel_item = event.GetItem() 620 self.__curr_node = sel_item 621 self.__update_text_for_selected_node() 622 return True
623 # #-------------------------------------------------------- 624 # def _on_mouse_motion(self, event): 625 # 626 # cursor_pos = (event.GetX(), event.GetY()) 627 # 628 # self.SetToolTipString(u'') 629 # 630 # if cursor_pos != self._old_cursor_pos: 631 # self._old_cursor_pos = cursor_pos 632 # (item, flags) = self.HitTest(cursor_pos) 633 # #if flags != wx.TREE_HITTEST_NOWHERE: 634 # if flags == wx.TREE_HITTEST_ONITEMLABEL: 635 # data = self.GetPyData(item) 636 # 637 # if not isinstance(data, gmEMRStructItems.cEncounter): 638 # return 639 # 640 # self.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 641 # data['started'].strftime('%x'), 642 # data['l10n_type'], 643 # data['started'].strftime('%H:%m'), 644 # data['last_affirmed'].strftime('%H:%m'), 645 # gmTools.coalesce(data['reason_for_encounter'], u''), 646 # gmTools.coalesce(data['assessment_of_encounter'], u'') 647 # )) 648 #--------------------------------------------------------
649 - def _on_tree_item_gettooltip(self, event):
650 651 item = event.GetItem() 652 653 if not item.IsOk(): 654 event.SetToolTip(u' ') 655 return 656 657 data = self.GetPyData(item) 658 659 if isinstance(data, gmEMRStructItems.cEncounter): 660 event.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 661 data['started'].strftime('%x'), 662 data['l10n_type'], 663 data['started'].strftime('%H:%M'), 664 data['last_affirmed'].strftime('%H:%M'), 665 gmTools.coalesce(data['reason_for_encounter'], u''), 666 gmTools.coalesce(data['assessment_of_encounter'], u'') 667 )) 668 669 elif isinstance(data, gmEMRStructItems.cEpisode): 670 tt = u'' 671 tt += gmTools.bool2subst ( 672 (data['diagnostic_certainty_classification'] is not None), 673 data.diagnostic_certainty_description + u'\n\n', 674 u'' 675 ) 676 tt += gmTools.bool2subst ( 677 data['episode_open'], 678 _('ongoing episode'), 679 _('closed episode'), 680 'error: episode state is None' 681 ) + u'\n' 682 tt += gmTools.coalesce(data['summary'], u'', u'\n%s') 683 tt = tt.strip(u'\n') 684 if tt == u'': 685 tt = u' ' 686 event.SetToolTip(tt) 687 688 elif isinstance(data, gmEMRStructItems.cHealthIssue): 689 tt = u'' 690 tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), u'') 691 tt += gmTools.bool2subst ( 692 (data['diagnostic_certainty_classification'] is not None), 693 data.diagnostic_certainty_description + u'\n', 694 u'' 695 ) 696 tt += gmTools.bool2subst ( 697 (data['laterality'] not in [None, u'na']), 698 data.laterality_description + u'\n', 699 u'' 700 ) 701 # noted_at_age is too costly 702 tt += gmTools.bool2subst(data['is_active'], _('active') + u'\n', u'') 703 tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + u'\n', u'') 704 tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + u'\n', u'') 705 tt += gmTools.coalesce(data['grouping'], u'\n', _('Grouping: %s') + u'\n\n') 706 tt += gmTools.coalesce(data['summary'], u'') 707 tt = tt.strip(u'\n') 708 if tt == u'': 709 tt = u' ' 710 event.SetToolTip(tt) 711 712 else: 713 event.SetToolTip(self.__root_tooltip)
714 #event.SetToolTip(u' ') 715 ##self.SetToolTipString(u'') 716 717 # doing this prevents the tooltip from showing at all 718 #event.Skip() 719 720 #widgetXY.GetToolTip().Enable(False) 721 # 722 #seems to work, supposing the tooltip is actually set for the widget, 723 #otherwise a test would be needed 724 #if widgetXY.GetToolTip(): 725 # widgetXY.GetToolTip().Enable(False) 726 #--------------------------------------------------------
727 - def _on_tree_item_right_clicked(self, event):
728 """Right button clicked: display the popup for the tree""" 729 730 node = event.GetItem() 731 self.SelectItem(node) 732 self.__curr_node_data = self.GetPyData(node) 733 self.__curr_node = node 734 735 pos = wx.DefaultPosition 736 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue): 737 self.__handle_issue_context(pos=pos) 738 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode): 739 self.__handle_episode_context(pos=pos) 740 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter): 741 self.__handle_encounter_context(pos=pos) 742 elif node == self.GetRootItem(): 743 self.__handle_root_context() 744 elif type(self.__curr_node_data) == type({}): 745 # ignore pseudo node "free-standing episodes" 746 pass 747 else: 748 print "error: unknown node type, no popup menu" 749 event.Skip()
750 #--------------------------------------------------------
751 - def OnCompareItems (self, node1=None, node2=None):
752 """Used in sorting items. 753 754 -1: 1 < 2 755 0: 1 = 2 756 1: 1 > 2 757 """ 758 # FIXME: implement sort modes, chron, reverse cron, by regex, etc 759 760 item1 = self.GetPyData(node1) 761 item2 = self.GetPyData(node2) 762 763 # encounters: reverse chron 764 if isinstance(item1, gmEMRStructItems.cEncounter): 765 if item1['started'] == item2['started']: 766 return 0 767 if item1['started'] > item2['started']: 768 return -1 769 return 1 770 771 # episodes: chron 772 if isinstance(item1, gmEMRStructItems.cEpisode): 773 start1 = item1.get_access_range()[0] 774 start2 = item2.get_access_range()[0] 775 if start1 == start2: 776 return 0 777 if start1 < start2: 778 return -1 779 return 1 780 781 # issues: alpha by grouping, no grouping at the bottom 782 if isinstance(item1, gmEMRStructItems.cHealthIssue): 783 784 # no grouping below grouping 785 if item1['grouping'] is None: 786 if item2['grouping'] is not None: 787 return 1 788 789 # grouping above no grouping 790 if item1['grouping'] is not None: 791 if item2['grouping'] is None: 792 return -1 793 794 # both no grouping: alpha on description 795 if (item1['grouping'] is None) and (item2['grouping'] is None): 796 if item1['description'].lower() < item2['description'].lower(): 797 return -1 798 if item1['description'].lower() > item2['description'].lower(): 799 return 1 800 return 0 801 802 # both with grouping: alpha on grouping, then alpha on description 803 if item1['grouping'] < item2['grouping']: 804 return -1 805 806 if item1['grouping'] > item2['grouping']: 807 return 1 808 809 if item1['description'].lower() < item2['description'].lower(): 810 return -1 811 812 if item1['description'].lower() > item2['description'].lower(): 813 return 1 814 815 return 0 816 817 # dummy health issue always on top 818 if isinstance(item1, type({})): 819 return -1 820 821 return 0
822 #================================================================ 823 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl 824
825 -class cScrolledEMRTreePnl(wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl):
826 """A scrollable panel holding an EMR tree. 827 828 Lacks a widget to display details for selected items. The 829 tree data will be refetched - if necessary - whenever 830 repopulate_ui() is called, e.g., when then patient is changed. 831 """
832 - def __init__(self, *args, **kwds):
834 # self.__register_interests() 835 #-------------------------------------------------------- 836 # def __register_interests(self): 837 # gmDispatcher.connect(signal= u'post_patient_selection', receiver=self.repopulate_ui) 838 #--------------------------------------------------------
839 - def repopulate_ui(self):
840 self._emr_tree.refresh() 841 return True
842 #============================================================ 843 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl 844
845 -class cSplittedEMRTreeBrowserPnl(wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl):
846 """A splitter window holding an EMR tree. 847 848 The left hand side displays a scrollable EMR tree while 849 on the right details for selected items are displayed. 850 851 Expects to be put into a Notebook. 852 """
853 - def __init__(self, *args, **kwds):
854 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds) 855 self._pnl_emr_tree._emr_tree.set_narrative_display(narrative_display = self._TCTRL_item_details) 856 self._pnl_emr_tree._emr_tree.set_image_display(image_display = self._PNL_visual_soap) 857 self.__register_events()
858 #--------------------------------------------------------
859 - def __register_events(self):
860 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 861 return True
862 #--------------------------------------------------------
863 - def _on_post_patient_selection(self):
864 if self.GetParent().GetCurrentPage() == self: 865 self.repopulate_ui() 866 return True
867 #--------------------------------------------------------
868 - def repopulate_ui(self):
869 """Fills UI with data.""" 870 self._pnl_emr_tree.repopulate_ui() 871 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True) 872 return True
873 #================================================================
874 -class cEMRJournalPanel(wx.Panel):
875 - def __init__(self, *args, **kwargs):
876 wx.Panel.__init__(self, *args, **kwargs) 877 878 self.__do_layout() 879 self.__register_events()
880 #--------------------------------------------------------
881 - def __do_layout(self):
882 self.__journal = wx.TextCtrl ( 883 self, 884 -1, 885 _('No EMR data loaded.'), 886 style = wx.TE_MULTILINE | wx.TE_READONLY 887 ) 888 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)) 889 # arrange widgets 890 szr_outer = wx.BoxSizer(wx.VERTICAL) 891 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0) 892 # and do layout 893 self.SetAutoLayout(1) 894 self.SetSizer(szr_outer) 895 szr_outer.Fit(self) 896 szr_outer.SetSizeHints(self) 897 self.Layout()
898 #--------------------------------------------------------
899 - def __register_events(self):
900 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
901 #--------------------------------------------------------
902 - def _on_post_patient_selection(self):
903 """Expects to be in a Notebook.""" 904 if self.GetParent().GetCurrentPage() == self: 905 self.repopulate_ui() 906 return True
907 #-------------------------------------------------------- 908 # notebook plugin API 909 #--------------------------------------------------------
910 - def repopulate_ui(self):
911 txt = StringIO.StringIO() 912 exporter = gmPatientExporter.cEMRJournalExporter() 913 # FIXME: if journal is large this will error out, use generator/yield etc 914 # FIXME: turn into proper list 915 try: 916 exporter.export(txt) 917 self.__journal.SetValue(txt.getvalue()) 918 except ValueError: 919 _log.exception('cannot get EMR journal') 920 self.__journal.SetValue (_( 921 'An error occurred while retrieving the EMR\n' 922 'in journal form for the active patient.\n\n' 923 'Please check the log file for details.' 924 )) 925 txt.close() 926 self.__journal.ShowPosition(self.__journal.GetLastPosition()) 927 return True
928 #================================================================ 929 # MAIN 930 #---------------------------------------------------------------- 931 if __name__ == '__main__': 932 933 _log.info("starting emr browser...") 934 935 try: 936 # obtain patient 937 patient = gmPersonSearch.ask_for_patient() 938 if patient is None: 939 print "No patient. Exiting gracefully..." 940 sys.exit(0) 941 gmPatSearchWidgets.set_active_patient(patient = patient) 942 943 # display standalone browser 944 application = wx.PyWidgetTester(size=(800,600)) 945 emr_browser = cEMRBrowserPanel(application.frame, -1) 946 emr_browser.refresh_tree() 947 948 application.frame.Show(True) 949 application.MainLoop() 950 951 # clean up 952 if patient is not None: 953 try: 954 patient.cleanup() 955 except: 956 print "error cleaning up patient" 957 except StandardError: 958 _log.exception("unhandled exception caught !") 959 # but re-raise them 960 raise 961 962 _log.info("closing emr browser...") 963 964 #================================================================ 965