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

Source Code for Module Gnumed.wxpython.gmEMRBrowser

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