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

# Source Code for Module Gnumed.wxpython.gmEMRBrowser

       1  """GNUmed patient EMR tree browser.
       2  """
       3  #================================================================
       4  __version__ = "$Revision: 1.111 $"
       5  __author__ = "cfmoro1976@yahoo.es, sjtan@swiftdsl.com.au, Karsten.Hilbert@gmx.net"
       6  __license__ = "GPL"
       7
       8  # std lib
       9  import sys, os.path, StringIO, codecs, logging
      10
      11
      12  # 3rd party
      13  import wx
      14
      15
      16  # GNUmed libs
      17  from Gnumed.pycommon import gmI18N, gmDispatcher, gmExceptions, gmTools
      18  from Gnumed.exporters import gmPatientExporter
      19  from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter, gmPersonSearch
      20  from Gnumed.wxpython import gmGuiHelpers
      21  from Gnumed.wxpython import gmEMRStructWidgets
      22  from Gnumed.wxpython import gmSOAPWidgets
      23  from Gnumed.wxpython import gmAllergyWidgets
      24  from Gnumed.wxpython import gmDemographicsWidgets
      25  from Gnumed.wxpython import gmNarrativeWidgets
      26  from Gnumed.wxpython import gmPatSearchWidgets
      27  from Gnumed.wxpython import gmVaccWidgets
      28  from Gnumed.wxpython import gmFamilyHistoryWidgets
      29
      30
      31  _log = logging.getLogger('gm.ui')
      32  _log.info(__version__)
      33
      34  #============================================================
      35 -def export_emr_to_ascii(parent=None):
      36          """
      37          Dump the patient's EMR from GUI client
      38          @param parent - The parent widget
      39          @type parent - A wx.Window instance
      40          """
      41          # sanity checks
      42          if parent is None:
      43                  raise TypeError('expected wx.Window instance as parent, got <None>')
      44
      45          pat = gmPerson.gmCurrentPatient()
      46          if not pat.connected:
      47                  gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.'))
      48                  return False
      49
      50          # get file name
      51          wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
      52          defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname'])))
      53          gmTools.mkdir(defdir)
      54          fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames'])
      55          dlg = wx.FileDialog (
      56                  parent = parent,
      57                  message = _("Save patient's EMR as..."),
      58                  defaultDir = defdir,
      59                  defaultFile = fname,
      60                  wildcard = wc,
      61                  style = wx.SAVE
      62          )
      63          choice = dlg.ShowModal()
      64          fname = dlg.GetPath()
      65          dlg.Destroy()
      66          if choice != wx.ID_OK:
      67                  return None
      68
      69          _log.debug('exporting EMR to [%s]', fname)
      70
      71  #       output_file = open(fname, 'wb')
      72          output_file = codecs.open(fname, 'wb', encoding='utf8', errors='replace')
      73          exporter = gmPatientExporter.cEmrExport(patient = pat)
      74          exporter.set_output_file(output_file)
      75          exporter.dump_constraints()
      76          exporter.dump_demographic_record(True)
      77          exporter.dump_clinical_record()
      78          exporter.dump_med_docs()
      79          output_file.close()
      80
      81          gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False)
      82          return fname
      83  #============================================================
      84 -class cEMRTree(wx.TreeCtrl, gmGuiHelpers.cTreeExpansionHistoryMixin):
      85          """This wx.TreeCtrl derivative displays a tree view of the medical record."""
      86
      87          #--------------------------------------------------------
      88 -        def __init__(self, parent, id, *args, **kwds):
      89                  """Set up our specialised tree.
      90                  """
      91                  kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE
      92                  wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
      93
      94                  gmGuiHelpers.cTreeExpansionHistoryMixin.__init__(self)
      95
      96                  self.__details_display = None
      97                  self.__details_display_mode = u'details'                                # "details" or "journal"
      98                  self.__enable_display_mode_selection = None
      99                  self.__pat = gmPerson.gmCurrentPatient()
     100                  self.__curr_node = None
     101                  self.__exporter = gmPatientExporter.cEmrExport(patient = self.__pat)
     102
     103                  self._old_cursor_pos = None
     104
     105                  self.__make_popup_menus()
     106                  self.__register_events()
     107          #--------------------------------------------------------
     108          # external API
     109          #--------------------------------------------------------
     110 -        def refresh(self):
     111                  if not self.__pat.connected:
     112                          gmDispatcher.send(signal='statustext', msg=_('Cannot load clinical narrative. No active patient.'),)
     113                          return False
     114
     115                  if not self.__populate_tree():
     116                          return False
     117
     118                  return True
     119          #--------------------------------------------------------
     120 -        def set_narrative_display(self, narrative_display=None):
     121                  self.__details_display = narrative_display
     122          #--------------------------------------------------------
     123 -        def set_image_display(self, image_display=None):
     124                  self.__img_display = image_display
     125          #--------------------------------------------------------
     126 -        def set_enable_display_mode_selection_callback(self, callback):
     127                  if not callable(callback):
     128                          raise ValueError('callback [%s] not callable' % callback)
     129
     130                  self.__enable_display_mode_selection = callback
     131          #--------------------------------------------------------
     132          # internal helpers
     133          #--------------------------------------------------------
     134 -        def __register_events(self):
     135                  """Configures enabled event signals."""
     136                  wx.EVT_TREE_SEL_CHANGED (self, self.GetId(), self._on_tree_item_selected)
     137                  wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self._on_tree_item_right_clicked)
     138
     139                  # handle tooltips
     140  #               wx.EVT_MOTION(self, self._on_mouse_motion)
     141                  wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip)
     142
     143                  gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db)
     144                  gmDispatcher.connect(signal = 'episode_mod_db', receiver = self._on_episode_mod_db)
     145                  gmDispatcher.connect(signal = 'health_issue_mod_db', receiver = self._on_issue_mod_db)
     146                  gmDispatcher.connect(signal = 'family_history_mod_db', receiver = self._on_issue_mod_db)
     147          #--------------------------------------------------------
     148 -        def __populate_tree(self):
     149                  """Updates EMR browser data."""
     150                  # FIXME: auto select the previously self.__curr_node if not None
     151                  # FIXME: error handling
     152
     153                  wx.BeginBusyCursor()
     154
     155  #               self.snapshot_expansion()
     156
     157                  # init new tree
     158                  self.DeleteAllItems()
     159                  root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name())
     160                  self.SetItemPyData(root_item, None)
     161                  self.SetItemHasChildren(root_item, True)
     162                  self.__root_tooltip = self.__pat['description_gender'] + u'\n'
     163                  if self.__pat['deceased'] is None:
     164                          self.__root_tooltip += u' %s (%s)\n\n' % (
     165                                  self.__pat.get_formatted_dob(format = '%d %b %Y', encoding = gmI18N.get_encoding()),
     166                                  self.__pat['medical_age']
     167                          )
     168                  else:
     169                          template = u' %s - %s (%s)\n\n'
     170                          self.__root_tooltip += template % (
     171                                  self.__pat.get_formatted_dob(format = '%d.%b %Y', encoding = gmI18N.get_encoding()),
     172                                  self.__pat['deceased'].strftime('%d.%b %Y').decode(gmI18N.get_encoding()),
     173                                  self.__pat['medical_age']
     174                          )
     175                  self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], u'', u'%s\n\n')
     176                  doc = self.__pat.primary_provider
     177                  if doc is not None:
     178                          self.__root_tooltip += u'%s:\n' % _('Primary provider in this praxis')
     179                          self.__root_tooltip += u' %s %s %s (%s)%s\n\n' % (
     180                                  gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])),
     181                                  doc['firstnames'],
     182                                  doc['lastnames'],
     183                                  doc['short_alias'],
     184                                  gmTools.bool2subst(doc['is_active'], u'', u' [%s]' % _('inactive'))
     185                          )
     186                  if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)):
     187                          self.__root_tooltip += _('In case of emergency contact:') + u'\n'
     188                          if self.__pat['emergency_contact'] is not None:
     189                                  self.__root_tooltip += gmTools.wrap (
     190                                          text = u'%s\n' % self.__pat['emergency_contact'],
     191                                          width = 60,
     192                                          initial_indent = u' ',
     193                                          subsequent_indent = u' '
     194                                  )
     195                          if self.__pat['pk_emergency_contact'] is not None:
     196                                  contact = self.__pat.emergency_contact_in_database
     197                                  self.__root_tooltip += u' %s\n' % contact['description_gender']
     198                  self.__root_tooltip = self.__root_tooltip.strip('\n')
     199                  if self.__root_tooltip == u'':
     200                          self.__root_tooltip = u' '
     201
     202                  # have the tree filled by the exporter
     203                  self.__exporter.get_historical_tree(self)
     204                  self.__curr_node = root_item
     205
     206                  self.SelectItem(root_item)
     207                  self.Expand(root_item)
     208                  self.__update_text_for_selected_node()
     209
     210  #               self.restore_expansion()
     211
     212                  wx.EndBusyCursor()
     213                  return True
     214          #--------------------------------------------------------
     215 -        def __update_text_for_selected_node(self):
     216                  """Displays information for the selected tree node."""
     217
     218                  if self.__details_display is None:
     219                          self.__img_display.clear()
     220                          return
     221
     222                  if self.__curr_node is None:
     223                          self.__img_display.clear()
     224                          return
     225
     226                  node_data = self.GetPyData(self.__curr_node)
     227                  doc_folder = self.__pat.get_document_folder()
     228
     229                  if isinstance(node_data, gmEMRStructItems.cHealthIssue):
     230                          self.__enable_display_mode_selection(True)
     231                          if self.__details_display_mode == u'details':
     232                                  txt = node_data.format(left_margin=1, patient = self.__pat)
     233                          else:
     234                                  txt = node_data.format_as_journal(left_margin = 1)
     235
     236                          self.__img_display.refresh (
     237                                  document_folder = doc_folder,
     238                                  episodes = [ epi['pk_episode'] for epi in node_data.episodes ]
     239                          )
     240
     241                  elif isinstance(node_data, type({})):
     242                          self.__enable_display_mode_selection(False)
     243                          # FIXME: turn into real dummy issue
     244                          txt = _('Pool of unassociated episodes:\n\n  "%s"') % node_data['description']
     245                          self.__img_display.clear()
     246
     247                  elif isinstance(node_data, gmEMRStructItems.cEpisode):
     248                          self.__enable_display_mode_selection(True)
     249                          if self.__details_display_mode == u'details':
     250                                  txt = node_data.format(left_margin = 1, patient = self.__pat)
     251                          else:
     252                                  txt = node_data.format_as_journal(left_margin = 1)
     253                          self.__img_display.refresh (
     254                                  document_folder = doc_folder,
     255                                  episodes = [node_data['pk_episode']]
     256                          )
     257
     258                  elif isinstance(node_data, gmEMRStructItems.cEncounter):
     259                          self.__enable_display_mode_selection(False)
     260                          epi = self.GetPyData(self.GetItemParent(self.__curr_node))
     261                          txt = node_data.format (
     262                                  episodes = [epi['pk_episode']],
     263                                  with_soap = True,
     264                                  left_margin = 1,
     265                                  patient = self.__pat,
     266                                  with_co_encountlet_hints = True
     267                          )
     268                          self.__img_display.refresh (
     269                                  document_folder = doc_folder,
     270                                  episodes = [epi['pk_episode']],
     271                                  encounter = node_data['pk_encounter']
     272                          )
     273
     274                  # root node == EMR level
     275                  else:
     276                          self.__enable_display_mode_selection(False)
     277                          emr = self.__pat.get_emr()
     278                          txt = emr.format_summary(dob = self.__pat['dob'])
     279                          self.__img_display.clear()
     280
     281                  self.__details_display.Clear()
     282                  self.__details_display.WriteText(txt)
     283                  self.__details_display.ShowPosition(0)
     284          #--------------------------------------------------------
     285 -        def __make_popup_menus(self):
     286
     287                  # - episodes
     288                  self.__epi_context_popup = wx.Menu(title = _('Episode Actions:'))
     289
     290                  menu_id = wx.NewId()
     291                  self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Edit details')))
     292                  wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__edit_episode)
     293
     294                  menu_id = wx.NewId()
     295                  self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Delete')))
     296                  wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__delete_episode)
     297
     298                  menu_id = wx.NewId()
     299                  self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Promote')))
     300                  wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__promote_episode_to_issue)
     301
     302                  menu_id = wx.NewId()
     303                  self.__epi_context_popup.AppendItem(wx.MenuItem(self.__epi_context_popup, menu_id, _('Move encounters')))
     304                  wx.EVT_MENU(self.__epi_context_popup, menu_id, self.__move_encounters)
     305
     306                  # - encounters
     307                  self.__enc_context_popup = wx.Menu(title = _('Encounter Actions:'))
     308                  # - move data
     309                  menu_id = wx.NewId()
     310                  self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Move data to another episode')))
     311                  wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__relink_encounter_data2episode)
     312                  # - edit encounter details
     313                  menu_id = wx.NewId()
     314                  self.__enc_context_popup.AppendItem(wx.MenuItem(self.__enc_context_popup, menu_id, _('Edit details')))
     315                  wx.EVT_MENU(self.__enc_context_popup, menu_id, self.__edit_encounter_details)
     316
     317                  item = self.__enc_context_popup.Append(-1, _('Edit progress notes'))
     318                  self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item)
     319
     320                  item = self.__enc_context_popup.Append(-1, _('Move progress notes'))
     321                  self.Bind(wx.EVT_MENU, self.__move_progress_notes, item)
     322
     323                  item = self.__enc_context_popup.Append(-1, _('Export for Medistar'))
     324                  self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item)
     325
     326                  # - health issues
     327                  self.__issue_context_popup = wx.Menu(title = _('Health Issue Actions:'))
     328
     329                  menu_id = wx.NewId()
     330                  self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Edit details')))
     331                  wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__edit_issue)
     332
     333                  menu_id = wx.NewId()
     334                  self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Delete')))
     335                  wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__delete_issue)
     336
     337                  self.__issue_context_popup.AppendSeparator()
     338
     339                  menu_id = wx.NewId()
     340                  self.__issue_context_popup.AppendItem(wx.MenuItem(self.__issue_context_popup, menu_id, _('Open to encounter level')))
     341                  wx.EVT_MENU(self.__issue_context_popup, menu_id, self.__expand_issue_to_encounter_level)
     342                  # print " attach issue to another patient"
     343                  # print " move all episodes to another issue"
     344
     345                  # - root node
     346                  self.__root_context_popup = wx.Menu(title = _('EMR Actions:'))
     347
     348                  menu_id = wx.NewId()
     349                  self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Create health issue')))
     350                  wx.EVT_MENU(self.__root_context_popup, menu_id, self.__create_issue)
     351
     352                  item = self.__root_context_popup.Append(-1, _('Create episode'))
     353                  self.Bind(wx.EVT_MENU, self.__create_episode, item)
     354
     355                  menu_id = wx.NewId()
     356                  self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage allergies')))
     357                  wx.EVT_MENU(self.__root_context_popup, menu_id, self.__document_allergy)
     358
     359                  menu_id = wx.NewId()
     360                  self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage family history')))
     361                  wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_family_history)
     362
     363                  menu_id = wx.NewId()
     364                  self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage hospitalizations')))
     365                  wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_hospital_stays)
     366
     367                  menu_id = wx.NewId()
     368                  self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage occupation')))
     369                  wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_occupation)
     370
     371                  menu_id = wx.NewId()
     372                  self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage procedures')))
     373                  wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_procedures)
     374
     375                  menu_id = wx.NewId()
     376                  self.__root_context_popup.AppendItem(wx.MenuItem(self.__root_context_popup, menu_id, _('Manage vaccinations')))
     377                  wx.EVT_MENU(self.__root_context_popup, menu_id, self.__manage_vaccinations)
     378
     379                  self.__root_context_popup.AppendSeparator()
     380
     381                  # expand tree
     382                  expand_menu = wx.Menu()
     383                  self.__root_context_popup.AppendMenu(wx.NewId(), _('Open EMR to ...'), expand_menu)
     384
     385                  menu_id = wx.NewId()
     386                  expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... issue level')))
     387                  wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_issue_level)
     388
     389                  menu_id = wx.NewId()
     390                  expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... episode level')))
     391                  wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_episode_level)
     392
     393                  menu_id = wx.NewId()
     394                  expand_menu.AppendItem(wx.MenuItem(expand_menu, menu_id, _('... encounter level')))
     395                  wx.EVT_MENU(expand_menu, menu_id, self.__expand_to_encounter_level)
     396          #--------------------------------------------------------
     397 -        def __handle_root_context(self, pos=wx.DefaultPosition):
     398                  self.PopupMenu(self.__root_context_popup, pos)
     399          #--------------------------------------------------------
     400 -        def __handle_issue_context(self, pos=wx.DefaultPosition):
     401  #               self.__issue_context_popup.SetTitle(_('Episode %s') % episode['description'])
     402                  self.PopupMenu(self.__issue_context_popup, pos)
     403          #--------------------------------------------------------
     404 -        def __handle_episode_context(self, pos=wx.DefaultPosition):
     405  #               self.__epi_context_popup.SetTitle(_('Episode %s') % self.__curr_node_data['description'])
     406                  self.PopupMenu(self.__epi_context_popup, pos)
     407          #--------------------------------------------------------
     408 -        def __handle_encounter_context(self, pos=wx.DefaultPosition):
     409                  self.PopupMenu(self.__enc_context_popup, pos)
     410          #--------------------------------------------------------
     411          # episode level
     412          #--------------------------------------------------------
     413 -        def __move_encounters(self, event):
     414                  episode = self.GetPyData(self.__curr_node)
     415
     416                  gmNarrativeWidgets.move_progress_notes_to_another_encounter (
     417                          parent = self,
     418                          episodes = [episode['pk_episode']],
     419                          move_all = True
     420                  )
     421          #--------------------------------------------------------
     422 -        def __edit_episode(self, event):
     423                  gmEMRStructWidgets.edit_episode(parent = self, episode = self.__curr_node_data)
     424          #--------------------------------------------------------
     425 -        def __promote_episode_to_issue(self, evt):
     426                  pat = gmPerson.gmCurrentPatient()
     427                  gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = pat.get_emr())
     428          #--------------------------------------------------------
     429 -        def __delete_episode(self, event):
     430                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
     431                          parent = self,
     432                          id = -1,
     433                          caption = _('Deleting episode'),
     434                          button_defs = [
     435                                  {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
     436                                  {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
     437                          ],
     438                          question = _(
     439                                  'Are you sure you want to delete this episode ?\n'
     440                                  '\n'
     441                                  ' "%s"\n'
     442                          ) % self.__curr_node_data['description']
     443                  )
     444                  result = dlg.ShowModal()
     445                  if result != wx.ID_YES:
     446                          return
     447
     448                  if not gmEMRStructItems.delete_episode(episode = self.__curr_node_data):
     449                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
     450          #--------------------------------------------------------
     451          # encounter level
     452          #--------------------------------------------------------
     453 -        def __move_progress_notes(self, evt):
     454                  encounter = self.GetPyData(self.__curr_node)
     455                  node_parent = self.GetItemParent(self.__curr_node)
     456                  episode = self.GetPyData(node_parent)
     457
     458                  gmNarrativeWidgets.move_progress_notes_to_another_encounter (
     459                          parent = self,
     460                          encounters = [encounter['pk_encounter']],
     461                          episodes = [episode['pk_episode']]
     462                  )
     463          #--------------------------------------------------------
     464 -        def __edit_progress_notes(self, event):
     465                  encounter = self.GetPyData(self.__curr_node)
     466                  node_parent = self.GetItemParent(self.__curr_node)
     467                  episode = self.GetPyData(node_parent)
     468
     469                  gmNarrativeWidgets.manage_progress_notes (
     470                          parent = self,
     471                          encounters = [encounter['pk_encounter']],
     472                          episodes = [episode['pk_episode']]
     473                  )
     474          #--------------------------------------------------------
     475 -        def __edit_encounter_details(self, event):
     476                  node_data = self.GetPyData(self.__curr_node)
     477                  gmEMRStructWidgets.edit_encounter(parent = self, encounter = node_data)
     478                  self.__populate_tree()
     479          #--------------------------------------------------------
     480 -        def __relink_encounter_data2episode(self, event):
     481
     482                  node_parent = self.GetItemParent(self.__curr_node)
     483                  owning_episode = self.GetPyData(node_parent)
     484
     485                  episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
     486                          self,
     487                          -1,
     488                          episode = owning_episode,
     489                          encounter = self.__curr_node_data
     490                  )
     491
     492                  result = episode_selector.ShowModal()
     493                  episode_selector.Destroy()
     494
     495                  if result == wx.ID_YES:
     496                          self.__populate_tree()
     497          #--------------------------------------------------------
     498          # issue level
     499          #--------------------------------------------------------
     500 -        def __edit_issue(self, event):
     501                  gmEMRStructWidgets.edit_health_issue(parent = self, issue = self.__curr_node_data)
     502          #--------------------------------------------------------
     503 -        def __delete_issue(self, event):
     504                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
     505                          parent = self,
     506                          id = -1,
     507                          caption = _('Deleting health issue'),
     508                          button_defs = [
     509                                  {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
     510                                  {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
     511                          ],
     512                          question = _(
     513                                  'Are you sure you want to delete this health issue ?\n'
     514                                  '\n'
     515                                  ' "%s"\n'
     516                          ) % self.__curr_node_data['description']
     517                  )
     518                  result = dlg.ShowModal()
     519                  if result != wx.ID_YES:
     520                          dlg.Destroy()
     521                          return
     522
     523                  dlg.Destroy()
     524
     525                  try:
     526                          gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data)
     527                  except gmExceptions.DatabaseObjectInUseError:
     528                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
     529          #--------------------------------------------------------
     530 -        def __expand_issue_to_encounter_level(self, evt):
     531
     532                  if not self.__curr_node.IsOk():
     533                          return
     534
     535                  self.Expand(self.__curr_node)
     536
     537                  epi, epi_cookie = self.GetFirstChild(self.__curr_node)
     538                  while epi.IsOk():
     539                          self.Expand(epi)
     540                          epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
     541          #--------------------------------------------------------
     542          # EMR level
     543          #--------------------------------------------------------
     544 -        def __create_issue(self, event):
     545                  gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
     546          #--------------------------------------------------------
     547 -        def __create_episode(self, event):
     548                  gmEMRStructWidgets.edit_episode(parent = self, episode = None)
     549          #--------------------------------------------------------
     550 -        def __document_allergy(self, event):
     551                  dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1)
     552                  # FIXME: use signal and use node level update
     553                  if dlg.ShowModal() == wx.ID_OK:
     554                          self.__populate_tree()
     555                  dlg.Destroy()
     556                  return
     557          #--------------------------------------------------------
     558 -        def __manage_procedures(self, event):
     559                  gmEMRStructWidgets.manage_performed_procedures(parent = self)
     560          #--------------------------------------------------------
     561 -        def __manage_family_history(self, event):
     562                  gmFamilyHistoryWidgets.manage_family_history(parent = self)
     563          #--------------------------------------------------------
     564 -        def __manage_hospital_stays(self, event):
     565                  gmEMRStructWidgets.manage_hospital_stays(parent = self)
     566          #--------------------------------------------------------
     567 -        def __manage_occupation(self, event):
     568                  gmDemographicsWidgets.edit_occupation()
     569          #--------------------------------------------------------
     570 -        def __manage_vaccinations(self, event):
     571                  gmVaccWidgets.manage_vaccinations(parent = self)
     572          #--------------------------------------------------------
     573 -        def __expand_to_issue_level(self, evt):
     574
     575                  root_item = self.GetRootItem()
     576
     577                  if not root_item.IsOk():
     578                          return
     579
     580                  self.Expand(root_item)
     581
     582                  # collapse episodes and issues
     583                  issue, issue_cookie = self.GetFirstChild(root_item)
     584                  while issue.IsOk():
     585                          self.Collapse(issue)
     586                          epi, epi_cookie = self.GetFirstChild(issue)
     587                          while epi.IsOk():
     588                                  self.Collapse(epi)
     589                                  epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
     590                          issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
     591          #--------------------------------------------------------
     592 -        def __expand_to_episode_level(self, evt):
     593
     594                  root_item = self.GetRootItem()
     595
     596                  if not root_item.IsOk():
     597                          return
     598
     599                  self.Expand(root_item)
     600
     601                  # collapse episodes, expand issues
     602                  issue, issue_cookie = self.GetFirstChild(root_item)
     603                  while issue.IsOk():
     604                          self.Expand(issue)
     605                          epi, epi_cookie = self.GetFirstChild(issue)
     606                          while epi.IsOk():
     607                                  self.Collapse(epi)
     608                                  epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
     609                          issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
     610          #--------------------------------------------------------
     611 -        def __expand_to_encounter_level(self, evt):
     612
     613                  root_item = self.GetRootItem()
     614
     615                  if not root_item.IsOk():
     616                          return
     617
     618                  self.Expand(root_item)
     619
     620                  # collapse episodes, expand issues
     621                  issue, issue_cookie = self.GetFirstChild(root_item)
     622                  while issue.IsOk():
     623                          self.Expand(issue)
     624                          epi, epi_cookie = self.GetFirstChild(issue)
     625                          while epi.IsOk():
     626                                  self.Expand(epi)
     627                                  epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
     628                          issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
     629          #--------------------------------------------------------
     630 -        def __export_encounter_for_medistar(self, evt):
     631                  gmNarrativeWidgets.export_narrative_for_medistar_import (
     632                          parent = self,
     633                          soap_cats = u'soapu',
     634                          encounter = self.__curr_node_data
     635                  )
     636          #--------------------------------------------------------
     637          # event handlers
     638          #--------------------------------------------------------
     639 -        def _on_narrative_mod_db(self, *args, **kwargs):
     640                  wx.CallAfter(self.__update_text_for_selected_node)
     641          #--------------------------------------------------------
     642 -        def _on_episode_mod_db(self, *args, **kwargs):
     643                  wx.CallAfter(self.__populate_tree)
     644          #--------------------------------------------------------
     645 -        def _on_issue_mod_db(self, *args, **kwargs):
     646                  wx.CallAfter(self.__populate_tree)
     647          #--------------------------------------------------------
     648 -        def _on_tree_item_selected(self, event):
     649                  sel_item = event.GetItem()
     650                  self.__curr_node = sel_item
     651                  self.__update_text_for_selected_node()
     652                  return True
     653  #       #--------------------------------------------------------
     654  #       def _on_mouse_motion(self, event):
     655  #
     656  #               cursor_pos = (event.GetX(), event.GetY())
     657  #
     658  #               self.SetToolTipString(u'')
     659  #
     660  #               if cursor_pos != self._old_cursor_pos:
     661  #                       self._old_cursor_pos = cursor_pos
     662  #                       (item, flags) = self.HitTest(cursor_pos)
     663  #                       #if flags != wx.TREE_HITTEST_NOWHERE:
     664  #                       if flags == wx.TREE_HITTEST_ONITEMLABEL:
     665  #                               data = self.GetPyData(item)
     666  #
     667  #                               if not isinstance(data, gmEMRStructItems.cEncounter):
     668  #                                       return
     669  #
     670  #                               self.SetToolTip(u'%s  %s  %s - %s\n\nRFE: %s\nAOE: %s' % (
     671  #                                       data['started'].strftime('%x'),
     672  #                                       data['l10n_type'],
     673  #                                       data['started'].strftime('%H:%m'),
     674  #                                       data['last_affirmed'].strftime('%H:%m'),
     675  #                                       gmTools.coalesce(data['reason_for_encounter'], u''),
     676  #                                       gmTools.coalesce(data['assessment_of_encounter'], u'')
     677  #                               ))
     678          #--------------------------------------------------------
     679 -        def _on_tree_item_gettooltip(self, event):
     680
     681                  item = event.GetItem()
     682
     683                  if not item.IsOk():
     684                          event.SetToolTip(u' ')
     685                          return
     686
     687                  data = self.GetPyData(item)
     688
     689                  if isinstance(data, gmEMRStructItems.cEncounter):
     690                          tt = u'%s  %s  %s - %s\n' % (
     691                                  data['started'].strftime('%x'),
     692                                  data['l10n_type'],
     693                                  data['started'].strftime('%H:%M'),
     694                                  data['last_affirmed'].strftime('%H:%M')
     695                          )
     696                          if data['reason_for_encounter'] is not None:
     697                                  tt += u'\n'
     698                                  tt += _('RFE: %s') % data['reason_for_encounter']
     699                                  if len(data['pk_generic_codes_rfe']) > 0:
     700                                          for code in data.generic_codes_rfe:
     701                                                  tt += u'\n %s: %s%s%s\n  (%s %s)' % (
     702                                                          code['code'],
     703                                                          gmTools.u_left_double_angle_quote,
     704                                                          code['term'],
     705                                                          gmTools.u_right_double_angle_quote,
     706                                                          code['name_short'],
     707                                                          code['version']
     708                                                  )
     709                          if data['assessment_of_encounter'] is not None:
     710                                  tt += u'\n'
     711                                  tt += _('AOE: %s') % data['assessment_of_encounter']
     712                                  if len(data['pk_generic_codes_aoe']) > 0:
     713                                          for code in data.generic_codes_aoe:
     714                                                  tt += u'\n %s: %s%s%s\n  (%s %s)' % (
     715                                                          code['code'],
     716                                                          gmTools.u_left_double_angle_quote,
     717                                                          code['term'],
     718                                                          gmTools.u_right_double_angle_quote,
     719                                                          code['name_short'],
     720                                                          code['version']
     721                                                  )
     722
     723                  elif isinstance(data, gmEMRStructItems.cEpisode):
     724                          tt = u''
     725                          tt += gmTools.bool2subst (
     726                                  (data['diagnostic_certainty_classification'] is not None),
     727                                  data.diagnostic_certainty_description + u'\n\n',
     728                                  u''
     729                          )
     730                          tt += gmTools.bool2subst (
     731                                  data['episode_open'],
     732                                  _('ongoing episode'),
     733                                  _('closed episode'),
     734                                  'error: episode state is None'
     735                          ) + u'\n'
     736                          tt += gmTools.coalesce(data['summary'], u'', u'\n%s')
     737                          if len(data['pk_generic_codes']) > 0:
     738                                  tt += u'\n'
     739                                  for code in data.generic_codes:
     740                                          tt += u'%s: %s%s%s\n  (%s %s)\n' % (
     741                                                  code['code'],
     742                                                  gmTools.u_left_double_angle_quote,
     743                                                  code['term'],
     744                                                  gmTools.u_right_double_angle_quote,
     745                                                  code['name_short'],
     746                                                  code['version']
     747                                          )
     748
     749                          tt = tt.strip(u'\n')
     750                          if tt == u'':
     751                                  tt = u' '
     752
     753                  elif isinstance(data, gmEMRStructItems.cHealthIssue):
     754                          tt = u''
     755                          tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), u'')
     756                          tt += gmTools.bool2subst (
     757                                  (data['diagnostic_certainty_classification'] is not None),
     758                                  data.diagnostic_certainty_description + u'\n',
     759                                  u''
     760                          )
     761                          tt += gmTools.bool2subst (
     762                                  (data['laterality'] not in [None, u'na']),
     763                                  data.laterality_description + u'\n',
     764                                  u''
     765                          )
     766                          # noted_at_age is too costly
     767                          tt += gmTools.bool2subst(data['is_active'], _('active') + u'\n', u'')
     768                          tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + u'\n', u'')
     769                          tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + u'\n', u'')
     770                          tt += gmTools.coalesce(data['grouping'], u'\n', _('Grouping: %s') + u'\n')
     771                          tt += gmTools.coalesce(data['summary'], u'', u'\n%s')
     772                          if len(data['pk_generic_codes']) > 0:
     773                                  tt += u'\n'
     774                                  for code in data.generic_codes:
     775                                          tt += u'%s: %s%s%s\n  (%s %s)\n' % (
     776                                                  code['code'],
     777                                                  gmTools.u_left_double_angle_quote,
     778                                                  code['term'],
     779                                                  gmTools.u_right_double_angle_quote,
     780                                                  code['name_short'],
     781                                                  code['version']
     782                                          )
     783
     784                          tt = tt.strip(u'\n')
     785                          if tt == u'':
     786                                  tt = u' '
     787
     788                  else:
     789                          tt = self.__root_tooltip
     790
     791                  event.SetToolTip(tt)
     792
     793                  # doing this prevents the tooltip from showing at all
     794                  #event.Skip()
     795
     796  #widgetXY.GetToolTip().Enable(False)
     797  #
     798  #seems to work, supposing the tooltip is actually set for the widget,
     799  #otherwise a test would be needed
     800  #if widgetXY.GetToolTip():
     801  #    widgetXY.GetToolTip().Enable(False)
     802          #--------------------------------------------------------
     803 -        def _on_tree_item_right_clicked(self, event):
     804                  """Right button clicked: display the popup for the tree"""
     805
     806                  node = event.GetItem()
     807                  self.SelectItem(node)
     808                  self.__curr_node_data = self.GetPyData(node)
     809                  self.__curr_node = node
     810
     811                  pos = wx.DefaultPosition
     812                  if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue):
     813                          self.__handle_issue_context(pos=pos)
     814                  elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode):
     815                          self.__handle_episode_context(pos=pos)
     816                  elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter):
     817                          self.__handle_encounter_context(pos=pos)
     818                  elif node == self.GetRootItem():
     819                          self.__handle_root_context()
     820                  elif type(self.__curr_node_data) == type({}):
     821                          # ignore pseudo node "free-standing episodes"
     822                          pass
     823                  else:
     824                          print "error: unknown node type, no popup menu"
     825                  event.Skip()
     826          #--------------------------------------------------------
     827 -        def OnCompareItems (self, node1=None, node2=None):
     828                  """Used in sorting items.
     829
     830                  -1: 1 < 2
     831                   0: 1 = 2
     832                   1: 1 > 2
     833                  """
     834                  # FIXME: implement sort modes, chron, reverse cron, by regex, etc
     835
     836                  if not node1:
     837                          _log.debug('invalid node 1')
     838                          return 0
     839                  if not node2:
     840                          _log.debug('invalid node 2')
     841                          return 0
     842
     843                  if not node1.IsOk():
     844                          _log.debug('invalid node 1')
     845                          return 0
     846                  if not node2.IsOk():
     847                          _log.debug('invalid node 2')
     848                          return 0
     849
     850                  item1 = self.GetPyData(node1)
     851                  item2 = self.GetPyData(node2)
     852
     853                  # dummy health issue always on top
     854                  if isinstance(item1, type({})):
     855                          return -1
     856                  if isinstance(item2, type({})):
     857                          return 1
     858
     859                  # encounters: reverse chronologically
     860                  if isinstance(item1, gmEMRStructItems.cEncounter):
     861                          if item1['started'] == item2['started']:
     862                                  return 0
     863                          if item1['started'] > item2['started']:
     864                                  return -1
     865                          return 1
     866
     867                  # episodes: chronologically
     868                  if isinstance(item1, gmEMRStructItems.cEpisode):
     869                          start1 = item1.get_access_range()[0]
     870                          start2 = item2.get_access_range()[0]
     871                          if start1 == start2:
     872                                  return 0
     873                          if start1 < start2:
     874                                  return -1
     875                          return 1
     876
     877                  # issues: alpha by grouping, no grouping at the bottom
     878                  if isinstance(item1, gmEMRStructItems.cHealthIssue):
     879
     880                          # no grouping below grouping
     881                          if item1['grouping'] is None:
     882                                  if item2['grouping'] is not None:
     883                                          return 1
     884
     885                          # grouping above no grouping
     886                          if item1['grouping'] is not None:
     887                                  if item2['grouping'] is None:
     888                                          return -1
     889
     890                          # both no grouping: alpha on description
     891                          if (item1['grouping'] is None) and (item2['grouping'] is None):
     892                                  if item1['description'].lower() < item2['description'].lower():
     893                                          return -1
     894                                  if item1['description'].lower() > item2['description'].lower():
     895                                          return 1
     896                                  return 0
     897
     898                          # both with grouping: alpha on grouping, then alpha on description
     899                          if item1['grouping'] < item2['grouping']:
     900                                  return -1
     901
     902                          if item1['grouping'] > item2['grouping']:
     903                                  return 1
     904
     905                          if item1['description'].lower() < item2['description'].lower():
     906                                  return -1
     907
     908                          if item1['description'].lower() > item2['description'].lower():
     909                                  return 1
     910
     911                          return 0
     912
     913                  _log.error('unknown item type during sorting EMR tree:')
     914                  _log.error('item1: %s', type(item1))
     915                  _log.error('item2: %s', type(item2))
     916
     917                  return 0
     918          #--------------------------------------------------------
     919          # properties
     920          #--------------------------------------------------------
     921 -        def _get_details_display_mode(self):
     922                  return self.__details_display_mode
     923
     924 -        def _set_details_display_mode(self, mode):
     925                  if mode not in [u'details', u'journal']:
     926                          raise ValueError('details display mode must be one of "details", "journal"')
     927                  if self.__details_display_mode == mode:
     928                          return
     929                  self.__details_display_mode = mode
     930                  self.__update_text_for_selected_node()
     931
     932          details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
     933  #================================================================
     934  from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl
     935
     936 -class cScrolledEMRTreePnl(wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl):
     937          """A scrollable panel holding an EMR tree.
     938
     939          Lacks a widget to display details for selected items. The
     940          tree data will be refetched - if necessary - whenever
     941          repopulate_ui() is called, e.g., when then patient is changed.
     942          """
     943 -        def __init__(self, *args, **kwds):
     944                  wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl.__init__(self, *args, **kwds)
     945          #--------------------------------------------------------
     946 -        def repopulate_ui(self):
     947                  self._emr_tree.refresh()
     948                  return True
     949  #============================================================
     950  from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl
     951
     952 -class cSplittedEMRTreeBrowserPnl(wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl):
     953          """A splitter window holding an EMR tree.
     954
     955          The left hand side displays a scrollable EMR tree while
     956          on the right details for selected items are displayed.
     957
     958          Expects to be put into a Notebook.
     959          """
     960 -        def __init__(self, *args, **kwds):
     961                  wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds)
     962                  self._pnl_emr_tree._emr_tree.set_narrative_display(narrative_display = self._TCTRL_item_details)
     963                  self._pnl_emr_tree._emr_tree.set_image_display(image_display = self._PNL_visual_soap)
     964                  self._pnl_emr_tree._emr_tree.set_enable_display_mode_selection_callback(self.enable_display_mode_selection)
     965                  self.__register_events()
     966          #--------------------------------------------------------
     967 -        def __register_events(self):
     968                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
     969                  return True
     970          #--------------------------------------------------------
     971          # event handler
     972          #--------------------------------------------------------
     973 -        def _on_post_patient_selection(self):
     974                  if self.GetParent().GetCurrentPage() == self:
     975                          self.repopulate_ui()
     976                  return True
     977          #--------------------------------------------------------
     978 -        def _on_show_details_selected(self, event):
     979                  #event.Skip()
     980                  self._pnl_emr_tree._emr_tree.details_display_mode = u'details'
     981          #--------------------------------------------------------
     982 -        def _on_show_journal_selected(self, event):
     983                  #event.Skip()
     984                  self._pnl_emr_tree._emr_tree.details_display_mode = u'journal'
     985          #--------------------------------------------------------
     986          # external API
     987          #--------------------------------------------------------
     988 -        def repopulate_ui(self):
     989                  """Fills UI with data."""
     990                  self._pnl_emr_tree.repopulate_ui()
     991                  self._splitter_browser.SetSashPosition(self._splitter_browser.GetSizeTuple()[0]/3, True)
     992                  return True
     993          #--------------------------------------------------------
     994 -        def enable_display_mode_selection(self, enable):
     995                  if enable:
     996                          self._RBTN_details.Enable(True)
     997                          self._RBTN_journal.Enable(True)
     998                          return
     999                  self._RBTN_details.Enable(False)
    1000                  self._RBTN_journal.Enable(False)
    1001  #================================================================
    1002 -class cEMRJournalPanel(wx.Panel):
    1003 -        def __init__(self, *args, **kwargs):
    1004                  wx.Panel.__init__(self, *args, **kwargs)
    1005
    1006                  self.__do_layout()
    1007                  self.__register_events()
    1008          #--------------------------------------------------------
    1009 -        def __do_layout(self):
    1010                  self.__journal = wx.TextCtrl (
    1011                          self,
    1012                          -1,
    1013                          _('No EMR data loaded.'),
    1014                          style = wx.TE_MULTILINE | wx.TE_READONLY
    1015                  )
    1016                  self.__journal.SetFont(wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL))
    1017                  # arrange widgets
    1018                  szr_outer = wx.BoxSizer(wx.VERTICAL)
    1019                  szr_outer.Add(self.__journal, 1, wx.EXPAND, 0)
    1020                  # and do layout
    1021                  self.SetAutoLayout(1)
    1022                  self.SetSizer(szr_outer)
    1023                  szr_outer.Fit(self)
    1024                  szr_outer.SetSizeHints(self)
    1025                  self.Layout()
    1026          #--------------------------------------------------------
    1027 -        def __register_events(self):
    1028                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
    1029          #--------------------------------------------------------
    1030 -        def _on_post_patient_selection(self):
    1031                  """Expects to be in a Notebook."""
    1032                  if self.GetParent().GetCurrentPage() == self:
    1033                          self.repopulate_ui()
    1034                  return True
    1035          #--------------------------------------------------------
    1036          # notebook plugin API
    1037          #--------------------------------------------------------
    1038 -        def repopulate_ui(self):
    1039                  txt = StringIO.StringIO()
    1040                  exporter = gmPatientExporter.cEMRJournalExporter()
    1041                  # FIXME: if journal is large this will error out, use generator/yield etc
    1042                  # FIXME: turn into proper list
    1043                  try:
    1044                          exporter.export(txt)
    1045                          self.__journal.SetValue(txt.getvalue())
    1046                  except ValueError:
    1047                          _log.exception('cannot get EMR journal')
    1048                          self.__journal.SetValue (_(
    1049                                  'An error occurred while retrieving the EMR\n'
    1050                                  'in journal form for the active patient.\n\n'
    1051                                  'Please check the log file for details.'
    1052                          ))
    1053                  txt.close()
    1054                  self.__journal.ShowPosition(self.__journal.GetLastPosition())
    1055                  return True
    1056  #================================================================
    1057  # MAIN
    1058  #----------------------------------------------------------------
    1059  if __name__ == '__main__':
    1060
    1061          _log.info("starting emr browser...")
    1062
    1063          try:
    1064                  # obtain patient
    1065                  patient = gmPersonSearch.ask_for_patient()
    1066                  if patient is None:
    1067                          print "No patient. Exiting gracefully..."
    1068                          sys.exit(0)
    1069                  gmPatSearchWidgets.set_active_patient(patient = patient)
    1070
    1071                  # display standalone browser
    1072                  application = wx.PyWidgetTester(size=(800,600))
    1073                  emr_browser = cEMRBrowserPanel(application.frame, -1)
    1074                  emr_browser.refresh_tree()
    1075
    1076                  application.frame.Show(True)
    1077                  application.MainLoop()
    1078
    1079                  # clean up
    1080                  if patient is not None:
    1081                          try:
    1082                                  patient.cleanup()
    1083                          except:
    1084                                  print "error cleaning up patient"
    1085          except StandardError:
    1086                  _log.exception("unhandled exception caught !")
    1087                  # but re-raise them
    1088                  raise
    1089
    1090          _log.info("closing emr browser...")
    1091
    1092  #================================================================
    1093