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          #--------------------------------------------------------
     149 -        def set_enable_display_mode_selection_callback(self, callback):
     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          #--------------------------------------------------------
     265 -        def __update_text_for_selected_node(self):
     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          #--------------------------------------------------------
     641 -        def __relink_encounter_data2episode(self, event):
     642
     643                  node_parent = self.GetItemParent(self.__curr_node)
     644                  owning_episode = self.GetPyData(node_parent)
     645
     646                  episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
     647                          self,
     648                          -1,
     649                          episode = owning_episode,
     650                          encounter = self.__curr_node_data
     651                  )
     652
     653                  result = episode_selector.ShowModal()
     654                  episode_selector.Destroy()
     655
     656                  if result == wx.ID_YES:
     657                          self.__populate_tree()
     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):
     765                  gmEMRStructWidgets.manage_performed_procedures(parent = self)
     766          #--------------------------------------------------------
     767 -        def __manage_family_history(self, event):
     768                  gmFamilyHistoryWidgets.manage_family_history(parent = self)
     769          #--------------------------------------------------------
     770 -        def __manage_hospital_stays(self, event):
     771                  gmEMRStructWidgets.manage_hospital_stays(parent = self)
     772          #--------------------------------------------------------
     773 -        def __manage_occupation(self, event):
     774                  gmDemographicsWidgets.edit_occupation()
     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):
    1202                  wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl.__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          #--------------------------------------------------------
    1276 -        def _on_switch_browse_edit_button_pressed(self, event):
    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