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