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 gmEMRStructWidgets.edit_encounter(parent = self, encounter = node_data) 452 self.__populate_tree()
453 #-------------------------------------------------------- 471 #-------------------------------------------------------- 472 # issue level 473 #--------------------------------------------------------
474 - def __edit_issue(self, event):
475 gmEMRStructWidgets.edit_health_issue(parent = self, issue = self.__curr_node_data)
476 #--------------------------------------------------------
477 - def __delete_issue(self, event):
478 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 479 parent = self, 480 id = -1, 481 caption = _('Deleting health issue'), 482 button_defs = [ 483 {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')}, 484 {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')} 485 ], 486 question = _( 487 'Are you sure you want to delete this health issue ?\n' 488 '\n' 489 ' "%s"\n' 490 ) % self.__curr_node_data['description'] 491 ) 492 result = dlg.ShowModal() 493 if result != wx.ID_YES: 494 dlg.Destroy() 495 return 496 497 dlg.Destroy() 498 499 try: 500 gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data) 501 except gmExceptions.DatabaseObjectInUseError: 502 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
503 #--------------------------------------------------------
505 506 if not self.__curr_node.IsOk(): 507 return 508 509 self.Expand(self.__curr_node) 510 511 epi, epi_cookie = self.GetFirstChild(self.__curr_node) 512 while epi.IsOk(): 513 self.Expand(epi) 514 epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
515 #-------------------------------------------------------- 516 # EMR level 517 #--------------------------------------------------------
518 - def __create_issue(self, event):
519 gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
520 #--------------------------------------------------------
521 - def __document_allergy(self, event):
522 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1) 523 # FIXME: use signal and use node level update 524 if dlg.ShowModal() == wx.ID_OK: 525 self.__populate_tree() 526 dlg.Destroy() 527 return
528 #--------------------------------------------------------
529 - def __manage_procedures(self, event):
531 #--------------------------------------------------------
532 - def __manage_hospital_stays(self, event):
534 #--------------------------------------------------------
535 - def __manage_occupation(self, event):
537 #--------------------------------------------------------
538 - def __manage_vaccinations(self, event):
540 #--------------------------------------------------------
541 - def __expand_to_issue_level(self, evt):
542 543 root_item = self.GetRootItem() 544 545 if not root_item.IsOk(): 546 return 547 548 self.Expand(root_item) 549 550 # collapse episodes and issues 551 issue, issue_cookie = self.GetFirstChild(root_item) 552 while issue.IsOk(): 553 self.Collapse(issue) 554 epi, epi_cookie = self.GetFirstChild(issue) 555 while epi.IsOk(): 556 self.Collapse(epi) 557 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 558 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
559 #--------------------------------------------------------
560 - def __expand_to_episode_level(self, evt):
561 562 root_item = self.GetRootItem() 563 564 if not root_item.IsOk(): 565 return 566 567 self.Expand(root_item) 568 569 # collapse episodes, expand issues 570 issue, issue_cookie = self.GetFirstChild(root_item) 571 while issue.IsOk(): 572 self.Expand(issue) 573 epi, epi_cookie = self.GetFirstChild(issue) 574 while epi.IsOk(): 575 self.Collapse(epi) 576 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 577 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
578 #--------------------------------------------------------
579 - def __expand_to_encounter_level(self, evt):
580 581 root_item = self.GetRootItem() 582 583 if not root_item.IsOk(): 584 return 585 586 self.Expand(root_item) 587 588 # collapse episodes, expand issues 589 issue, issue_cookie = self.GetFirstChild(root_item) 590 while issue.IsOk(): 591 self.Expand(issue) 592 epi, epi_cookie = self.GetFirstChild(issue) 593 while epi.IsOk(): 594 self.Expand(epi) 595 epi, epi_cookie = self.GetNextChild(issue, epi_cookie) 596 issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
597 #--------------------------------------------------------
598 - def __export_encounter_for_medistar(self, evt):
599 gmNarrativeWidgets.export_narrative_for_medistar_import ( 600 parent = self, 601 soap_cats = u'soap', 602 encounter = self.__curr_node_data 603 )
604 #-------------------------------------------------------- 605 # event handlers 606 #--------------------------------------------------------
607 - def _on_narrative_mod_db(self, *args, **kwargs):
608 wx.CallAfter(self.__update_text_for_selected_node)
609 #--------------------------------------------------------
610 - def _on_episode_mod_db(self, *args, **kwargs):
611 wx.CallAfter(self.__populate_tree)
612 #--------------------------------------------------------
613 - def _on_issue_mod_db(self, *args, **kwargs):
614 wx.CallAfter(self.__populate_tree)
615 #--------------------------------------------------------
616 - def _on_tree_item_selected(self, event):
617 sel_item = event.GetItem() 618 self.__curr_node = sel_item 619 self.__update_text_for_selected_node() 620 return True
621 # #-------------------------------------------------------- 622 # def _on_mouse_motion(self, event): 623 # 624 # cursor_pos = (event.GetX(), event.GetY()) 625 # 626 # self.SetToolTipString(u'') 627 # 628 # if cursor_pos != self._old_cursor_pos: 629 # self._old_cursor_pos = cursor_pos 630 # (item, flags) = self.HitTest(cursor_pos) 631 # #if flags != wx.TREE_HITTEST_NOWHERE: 632 # if flags == wx.TREE_HITTEST_ONITEMLABEL: 633 # data = self.GetPyData(item) 634 # 635 # if not isinstance(data, gmEMRStructItems.cEncounter): 636 # return 637 # 638 # self.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 639 # data['started'].strftime('%x'), 640 # data['l10n_type'], 641 # data['started'].strftime('%H:%m'), 642 # data['last_affirmed'].strftime('%H:%m'), 643 # gmTools.coalesce(data['reason_for_encounter'], u''), 644 # gmTools.coalesce(data['assessment_of_encounter'], u'') 645 # )) 646 #--------------------------------------------------------
647 - def _on_tree_item_gettooltip(self, event):
648 649 item = event.GetItem() 650 651 if not item.IsOk(): 652 event.SetToolTip(u' ') 653 return 654 655 data = self.GetPyData(item) 656 657 if isinstance(data, gmEMRStructItems.cEncounter): 658 event.SetToolTip(u'%s %s %s - %s\n\nRFE: %s\nAOE: %s' % ( 659 data['started'].strftime('%x'), 660 data['l10n_type'], 661 data['started'].strftime('%H:%M'), 662 data['last_affirmed'].strftime('%H:%M'), 663 gmTools.coalesce(data['reason_for_encounter'], u''), 664 gmTools.coalesce(data['assessment_of_encounter'], u'') 665 )) 666 667 elif isinstance(data, gmEMRStructItems.cEpisode): 668 tt = u'' 669 tt += gmTools.bool2subst ( 670 (data['diagnostic_certainty_classification'] is not None), 671 data.diagnostic_certainty_description + u'\n\n', 672 u'' 673 ) 674 tt += gmTools.bool2subst ( 675 data['episode_open'], 676 _('ongoing episode'), 677 _('closed episode'), 678 'error: episode state is None' 679 ) + u'\n' 680 tt += gmTools.coalesce(data['summary'], u'', u'\n%s') 681 tt = tt.strip(u'\n') 682 if tt == u'': 683 tt = u' ' 684 event.SetToolTip(tt) 685 686 elif isinstance(data, gmEMRStructItems.cHealthIssue): 687 tt = u'' 688 tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), u'') 689 tt += gmTools.bool2subst ( 690 (data['diagnostic_certainty_classification'] is not None), 691 data.diagnostic_certainty_description + u'\n', 692 u'' 693 ) 694 tt += gmTools.bool2subst ( 695 (data['laterality'] not in [None, u'na']), 696 data.laterality_description + u'\n', 697 u'' 698 ) 699 # noted_at_age is too costly 700 tt += gmTools.bool2subst(data['is_active'], _('active') + u'\n', u'') 701 tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + u'\n', u'') 702 tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + u'\n', u'') 703 tt += gmTools.coalesce(data['grouping'], u'\n', _('Grouping: %s') + u'\n\n') 704 tt += gmTools.coalesce(data['summary'], u'') 705 tt = tt.strip(u'\n') 706 if tt == u'': 707 tt = u' ' 708 event.SetToolTip(tt) 709 710 else: 711 event.SetToolTip(self.__root_tooltip)
712 #event.SetToolTip(u' ') 713 ##self.SetToolTipString(u'') 714 715 # doing this prevents the tooltip from showing at all 716 #event.Skip() 717 718 #widgetXY.GetToolTip().Enable(False) 719 # 720 #seems to work, supposing the tooltip is actually set for the widget, 721 #otherwise a test would be needed 722 #if widgetXY.GetToolTip(): 723 # widgetXY.GetToolTip().Enable(False) 724 #--------------------------------------------------------
725 - def _on_tree_item_right_clicked(self, event):
726 """Right button clicked: display the popup for the tree""" 727 728 node = event.GetItem() 729 self.SelectItem(node) 730 self.__curr_node_data = self.GetPyData(node) 731 self.__curr_node = node 732 733 pos = wx.DefaultPosition 734 if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue): 735 self.__handle_issue_context(pos=pos) 736 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode): 737 self.__handle_episode_context(pos=pos) 738 elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter): 739 self.__handle_encounter_context(pos=pos) 740 elif node == self.GetRootItem(): 741 self.__handle_root_context() 742 elif type(self.__curr_node_data) == type({}): 743 # ignore pseudo node "free-standing episodes" 744 pass 745 else: 746 print "error: unknown node type, no popup menu" 747 event.Skip()
748 #--------------------------------------------------------
749 - def OnCompareItems (self, node1=None, node2=None):
750 """Used in sorting items. 751 752 -1: 1 < 2 753 0: 1 = 2 754 1: 1 > 2 755 """ 756 # FIXME: implement sort modes, chron, reverse cron, by regex, etc 757 758 item1 = self.GetPyData(node1) 759 item2 = self.GetPyData(node2) 760 761 # encounters: reverse chron 762 if isinstance(item1, gmEMRStructItems.cEncounter): 763 if item1['started'] == item2['started']: 764 return 0 765 if item1['started'] > item2['started']: 766 return -1 767 return 1 768 769 # episodes: chron 770 if isinstance(item1, gmEMRStructItems.cEpisode): 771 start1 = item1.get_access_range()[0] 772 start2 = item2.get_access_range()[0] 773 if start1 == start2: 774 return 0 775 if start1 < start2: 776 return -1 777 return 1 778 779 # issues: alpha by grouping, no grouping at the bottom 780 if isinstance(item1, gmEMRStructItems.cHealthIssue): 781 782 # no grouping below grouping 783 if item1['grouping'] is None: 784 if item2['grouping'] is not None: 785 return 1 786 787 # grouping above no grouping 788 if item1['grouping'] is not None: 789 if item2['grouping'] is None: 790 return -1 791 792 # both no grouping: alpha on description 793 if (item1['grouping'] is None) and (item2['grouping'] is None): 794 if item1['description'].lower() < item2['description'].lower(): 795 return -1 796 if item1['description'].lower() > item2['description'].lower(): 797 return 1 798 return 0 799 800 # both with grouping: alpha on grouping, then alpha on description 801 if item1['grouping'] < item2['grouping']: 802 return -1 803 804 if item1['grouping'] > item2['grouping']: 805 return 1 806 807 if item1['description'].lower() < item2['description'].lower(): 808 return -1 809 810 if item1['description'].lower() > item2['description'].lower(): 811 return 1 812 813 return 0 814 815 # dummy health issue always on top 816 if isinstance(item1, type({})): 817 return -1 818 819 return 0
820 #================================================================ 821 from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl 822
823 -class cScrolledEMRTreePnl(wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl):
824 """A scrollable panel holding an EMR tree. 825 826 Lacks a widget to display details for selected items. The 827 tree data will be refetched - if necessary - whenever 828 repopulate_ui() is called, e.g., when then patient is changed. 829 """
830 - def __init__(self, *args, **kwds):
832 # self.__register_interests() 833 #-------------------------------------------------------- 834 # def __register_interests(self): 835 # gmDispatcher.connect(signal= u'post_patient_selection', receiver=self.repopulate_ui) 836 #--------------------------------------------------------
837 - def repopulate_ui(self):
838 self._emr_tree.refresh() 839 return True
840 #============================================================ 841 from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl 842
843 -class cSplittedEMRTreeBrowserPnl(wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl):
844 """A splitter window holding an EMR tree. 845 846 The left hand side displays a scrollable EMR tree while 847 on the right details for selected items are displayed. 848 849 Expects to be put into a Notebook. 850 """
851 - def __init__(self, *args, **kwds):
852 wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds) 853 self._pnl_emr_tree._emr_tree.set_narrative_display(narrative_display = self._TCTRL_item_details) 854 self._pnl_emr_tree._emr_tree.set_image_display(image_display = self._PNL_visual_soap) 855 self.__register_events()
856 #--------------------------------------------------------
857 - def __register_events(self):
858 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 859 return True
860 #--------------------------------------------------------
861 - def _on_post_patient_selection(self):
862 if self.GetParent().GetCurrentPage() == self: 863 self.repopulate_ui() 864 return True
865 #--------------------------------------------------------
866 - def repopulate_ui(self):
867 """Fills UI with data.""" 868 self._pnl_emr_tree.repopulate_ui() 869 self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True) 870 return True
871 #================================================================
872 -class cEMRJournalPanel(wx.Panel):
873 - def __init__(self, *args, **kwargs):
874 wx.Panel.__init__(self, *args, **kwargs) 875 876 self.__do_layout() 877 self.__register_events()
878 #--------------------------------------------------------
879 - def __do_layout(self):
880 self.__journal = wx.TextCtrl ( 881 self, 882 -1, 883 _('No EMR data loaded.'), 884 style = wx.TE_MULTILINE | wx.TE_READONLY 885 ) 886 self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)) 887 # arrange widgets 888 szr_outer = wx.BoxSizer(wx.VERTICAL) 889 szr_outer.Add(self.__journal, 1, wx.EXPAND, 0) 890 # and do layout 891 self.SetAutoLayout(1) 892 self.SetSizer(szr_outer) 893 szr_outer.Fit(self) 894 szr_outer.SetSizeHints(self) 895 self.Layout()
896 #--------------------------------------------------------
897 - def __register_events(self):
898 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
899 #--------------------------------------------------------
900 - def _on_post_patient_selection(self):
901 """Expects to be in a Notebook.""" 902 if self.GetParent().GetCurrentPage() == self: 903 self.repopulate_ui() 904 return True
905 #-------------------------------------------------------- 906 # notebook plugin API 907 #--------------------------------------------------------
908 - def repopulate_ui(self):
909 txt = StringIO.StringIO() 910 exporter = gmPatientExporter.cEMRJournalExporter() 911 # FIXME: if journal is large this will error out, use generator/yield etc 912 # FIXME: turn into proper list 913 try: 914 exporter.export(txt) 915 self.__journal.SetValue(txt.getvalue()) 916 except ValueError: 917 _log.exception('cannot get EMR journal') 918 self.__journal.SetValue (_( 919 'An error occurred while retrieving the EMR\n' 920 'in journal form for the active patient.\n\n' 921 'Please check the log file for details.' 922 )) 923 txt.close() 924 self.__journal.ShowPosition(self.__journal.GetLastPosition()) 925 return True
926 #================================================================ 927 # MAIN 928 #---------------------------------------------------------------- 929 if __name__ == '__main__': 930 931 _log.info("starting emr browser...") 932 933 try: 934 # obtain patient 935 patient = gmPersonSearch.ask_for_patient() 936 if patient is None: 937 print "No patient. Exiting gracefully..." 938 sys.exit(0) 939 gmPatSearchWidgets.set_active_patient(patient = patient) 940 941 # display standalone browser 942 application = wx.PyWidgetTester(size=(800,600)) 943 emr_browser = cEMRBrowserPanel(application.frame, -1) 944 emr_browser.refresh_tree() 945 946 application.frame.Show(True) 947 application.MainLoop() 948 949 # clean up 950 if patient is not None: 951 try: 952 patient.cleanup() 953 except: 954 print "error cleaning up patient" 955 except StandardError: 956 _log.exception("unhandled exception caught !") 957 # but re-raise them 958 raise 959 960 _log.info("closing emr browser...") 961 962 #================================================================ 963