Gnumed.wxpython.gmEMRBrowser

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 io
      10  import logging
      11
      12
      13  # 3rd party
      14  import wx
      15  import wx.lib.mixins.treemixin as treemixin
      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  from Gnumed.business import gmSoapDefs
      33  from Gnumed.business import gmClinicalRecord
      34
      35  from Gnumed.wxpython import gmGuiHelpers
      36  from Gnumed.wxpython import gmEMRStructWidgets
      37  from Gnumed.wxpython import gmEncounterWidgets
      38  from Gnumed.wxpython import gmSOAPWidgets
      39  from Gnumed.wxpython import gmAllergyWidgets
      40  from Gnumed.wxpython import gmDemographicsWidgets
      41  from Gnumed.wxpython import gmNarrativeWidgets
      42  from Gnumed.wxpython import gmNarrativeWorkflows
      43  from Gnumed.wxpython import gmPatSearchWidgets
      44  from Gnumed.wxpython import gmVaccWidgets
      45  from Gnumed.wxpython import gmFamilyHistoryWidgets
      46  from Gnumed.wxpython import gmFormWidgets
      47  from Gnumed.wxpython import gmTimer
      48  from Gnumed.wxpython import gmHospitalStayWidgets
      49  from Gnumed.wxpython import gmProcedureWidgets
      50
      51
      52  _log = logging.getLogger('gm.ui')
      53
      54  #============================================================
      55 -def export_emr_to_ascii(parent=None):
      56          """
      57          Dump the patient's EMR from GUI client
      58          @param parent - The parent widget
      59          @type parent - A wx.Window instance
      60          """
      61          # sanity checks
      62          if parent is None:
      63                  raise TypeError('expected wx.Window instance as parent, got <None>')
      64
      65          pat = gmPerson.gmCurrentPatient()
      66          if not pat.connected:
      67                  gmDispatcher.send(signal='statustext', msg=_('Cannot export EMR. No active patient.'))
      68                  return False
      69
      70          # get file name
      71          wc = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
      72          defdir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed', pat.subdir_name)))
      73          gmTools.mkdir(defdir)
      74          fname = '%s-%s_%s.txt' % (_('emr-export'), pat['lastnames'], pat['firstnames'])
      75          dlg = wx.FileDialog (
      76                  parent = parent,
      77                  message = _("Save patient's EMR as..."),
      78                  defaultDir = defdir,
      79                  defaultFile = fname,
      80                  wildcard = wc,
      81                  style = wx.FD_SAVE
      82          )
      83          choice = dlg.ShowModal()
      84          fname = dlg.GetPath()
      85          dlg.Destroy()
      86          if choice != wx.ID_OK:
      87                  return None
      88
      89          _log.debug('exporting EMR to [%s]', fname)
      90
      91          output_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace')
      92          exporter = gmPatientExporter.cEmrExport(patient = pat)
      93          exporter.set_output_file(output_file)
      94          exporter.dump_constraints()
      95          exporter.dump_demographic_record(True)
      96          exporter.dump_clinical_record()
      97          exporter.dump_med_docs()
      98          output_file.close()
      99
     100          gmDispatcher.send('statustext', msg = _('EMR successfully exported to file: %s') % fname, beep = False)
     101          return fname
     102
     103  #============================================================
     104 -class cEMRTree(wx.TreeCtrl, treemixin.ExpansionState):
     105          """This wx.TreeCtrl derivative displays a tree view of a medical record."""
     106
     107          #--------------------------------------------------------
     108 -        def __init__(self, parent, id, *args, **kwds):
     109                  """Set up our specialised tree.
     110                  """
     111                  kwds['style'] = wx.TR_HAS_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE
     112                  wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
     113
     114                  self.__soap_display = None
     115                  self.__soap_display_mode = 'details'                            # "details" or "journal" or "revisions"
     116                  self.__img_display = None
     117                  self.__cb__enable_display_mode_selection = lambda x:x
     118                  self.__cb__select_edit_mode = lambda x:x
     119                  self.__cb__add_soap_editor = lambda x:x
     120                  self.__pat = None
     121                  self.__curr_node = None
     122                  self.__expanded_nodes = None
     123
     124                  self.__make_popup_menus()
     125                  self.__register_events()
     126
     127          #--------------------------------------------------------
     128          # external API
     129          #--------------------------------------------------------
     130 -        def _get_soap_display(self):
     131                  return self.__soap_display
     132
     133 -        def _set_soap_display(self, soap_display=None):
     134                  self.__soap_display = soap_display
     135                  self.__soap_display_prop_font = soap_display.GetFont()
     136                  self.__soap_display_mono_font = wx.Font(self.__soap_display_prop_font.GetNativeFontInfo())
     137                  self.__soap_display_mono_font.SetFamily(wx.FONTFAMILY_TELETYPE)
     138                  self.__soap_display_mono_font.SetPointSize(self.__soap_display_prop_font.GetPointSize() - 2)
     139
     140          soap_display = property(_get_soap_display, _set_soap_display)
     141
     142          #--------------------------------------------------------
     143 -        def _get_image_display(self):
     144                  return self.__img_display
     145
     146 -        def _set_image_display(self, image_display=None):
     147                  self.__img_display = image_display
     148
     149          image_display = property(_get_image_display, _set_image_display)
     150
     151          #--------------------------------------------------------
     152 -        def set_enable_display_mode_selection_callback(self, callback):
     153                  if not callable(callback):
     154                          raise ValueError('callback [%s] not callable' % callback)
     155                  self.__cb__enable_display_mode_selection = callback
     156
     157          #--------------------------------------------------------
     158 -        def _set_edit_mode_selector(self, callback):
     159                  if callback is None:
     160                          callback = lambda x:x
     161                  if not callable(callback):
     162                          raise ValueError('edit mode selector [%s] not callable' % callback)
     163                  self.__cb__select_edit_mode = callback
     164
     165          edit_mode_selector = property(lambda x:x, _set_edit_mode_selector)
     166
     167          #--------------------------------------------------------
     168 -        def _set_soap_editor_adder(self, callback):
     169                  if callback is None:
     170                          callback = lambda x:x
     171                  if not callable(callback):
     172                          raise ValueError('soap editor adder [%s] not callable' % callback)
     173                  self.__cb__add_soap_editor = callback
     174
     175          soap_editor_adder = property(lambda x:x, _set_soap_editor_adder)
     176
     177          #--------------------------------------------------------
     178          # ExpansionState mixin API
     179          #--------------------------------------------------------
     180 -        def GetItemIdentity(self, item):
     181                  if item is None:
     182                          return 'invalid item'
     183
     184                  if not item.IsOk():
     185                          return 'invalid item'
     186
     187                  try:
     188                          node_data = self.GetItemData(item)
     189                  except wx.wxAssertionError:
     190                          _log.exception('unfathomable self.GetItemData() problem occurred, faking root node')
     191                          _log.error('real node: %s', item)
     192                          _log.error('node.IsOk(): %s', item.IsOk())              # already survived this further up
     193                          _log.error('is root node: %s', item == self.GetRootItem())
     194                          _log.error('node attributes: %s', dir(item))
     195                          gmLog2.log_stack_trace()
     196                          return 'invalid item'
     197
     198                  if isinstance(node_data, gmEMRStructItems.cHealthIssue):
     199                          return 'issue::%s' % node_data['pk_health_issue']
     200                  if isinstance(node_data, gmEMRStructItems.cEpisode):
     201                          return 'episode::%s' % node_data['pk_episode']
     202                  if isinstance(node_data, gmEMRStructItems.cEncounter):
     203                          return 'encounter::%s' % node_data['pk_encounter']
     204                  # unassociated episodes
     205                  if isinstance(node_data, type({})):
     206                          return 'dummy node::%s' % self.__pat.ID
     207                  # root node == EMR level
     208                  return 'root node::%s' % self.__pat.ID
     209
     210          #--------------------------------------------------------
     211          # internal helpers
     212          #--------------------------------------------------------
     213 -        def __register_events(self):
     214                  """Configures enabled event signals."""
     215                  self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_tree_item_selected)
     216                  self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self._on_tree_item_right_clicked)
     217                  self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._on_tree_item_expanding)
     218
     219                  # handle tooltips
     220  #               self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
     221                  self.Bind(wx.EVT_TREE_ITEM_GETTOOLTIP, self._on_tree_item_gettooltip)
     222
     223                  # FIXME: xxxxx signal
     224                  gmDispatcher.connect(signal = 'narrative_mod_db', receiver = self._on_narrative_mod_db)
     225                  gmDispatcher.connect(signal = 'clin.episode_mod_db', receiver = self._on_episode_mod_db)
     226                  gmDispatcher.connect(signal = 'clin.health_issue_mod_db', receiver = self._on_issue_mod_db)
     227                  gmDispatcher.connect(signal = 'clin.family_history_mod_db', receiver = self._on_issue_mod_db)
     228
     229          #--------------------------------------------------------
     230 -        def clear_tree(self):
     231                  self.DeleteAllItems()
     232                  self.__expanded_nodes = None
     233
     234          #--------------------------------------------------------
     235 -        def __populate_tree(self):
     236                  """Updates EMR browser data."""
     237                  # FIXME: auto select the previously self.__curr_node if not None
     238                  # FIXME: error handling
     239
     240                  _log.debug('populating EMR tree')
     241
     242                  wx.BeginBusyCursor()
     243
     244                  if self.__pat is None:
     245                          self.clear_tree()
     246                          self.__expanded_nodes = None
     247                          wx.EndBusyCursor()
     248                          return True
     249
     250                  # init new tree
     251                  root_item = self.__populate_root_node()
     252                  self.__curr_node = root_item
     253                  if self.__expanded_nodes is not None:
     254                          self.ExpansionState = self.__expanded_nodes
     255                  self.SelectItem(root_item)
     256                  self.Expand(root_item)
     257                  self.__update_text_for_selected_node()                                  # this is fairly slow, too
     258
     259                  wx.EndBusyCursor()
     260                  return True
     261
     262          #--------------------------------------------------------
     263 -        def __populate_root_node(self):
     264
     265                  self.DeleteAllItems()
     266
     267                  root_item = self.AddRoot(_('EMR of %(lastnames)s, %(firstnames)s') % self.__pat.get_active_name())
     268                  self.SetItemData(root_item, None)
     269                  self.SetItemHasChildren(root_item, True)
     270
     271                  self.__root_tooltip = self.__pat['description_gender'] + '\n'
     272                  if self.__pat['deceased'] is None:
     273                          self.__root_tooltip += ' %s (%s)\n\n' % (
     274                                  self.__pat.get_formatted_dob(format = '%d %b %Y'),
     275                                  self.__pat['medical_age']
     276                          )
     277                  else:
     278                          template = ' %s - %s (%s)\n\n'
     279                          self.__root_tooltip += template % (
     280                                  self.__pat.get_formatted_dob(format = '%d.%b %Y'),
     281                                  gmDateTime.pydt_strftime(self.__pat['deceased'], '%Y %b %d'),
     282                                  self.__pat['medical_age']
     283                          )
     284                  self.__root_tooltip += gmTools.coalesce(self.__pat['comment'], '', '%s\n\n')
     285                  doc = self.__pat.primary_provider
     286                  if doc is not None:
     287                          self.__root_tooltip += '%s:\n' % _('Primary provider in this praxis')
     288                          self.__root_tooltip += ' %s %s %s (%s)%s\n\n' % (
     289                                  gmTools.coalesce(doc['title'], gmPerson.map_gender2salutation(gender = doc['gender'])),
     290                                  doc['firstnames'],
     291                                  doc['lastnames'],
     292                                  doc['short_alias'],
     293                                  gmTools.bool2subst(doc['is_active'], '', ' [%s]' % _('inactive'))
     294                          )
     295                  if not ((self.__pat['emergency_contact'] is None) and (self.__pat['pk_emergency_contact'] is None)):
     296                          self.__root_tooltip += _('In case of emergency contact:') + '\n'
     297                          if self.__pat['emergency_contact'] is not None:
     298                                  self.__root_tooltip += gmTools.wrap (
     299                                          text = '%s\n' % self.__pat['emergency_contact'],
     300                                          width = 60,
     301                                          initial_indent = ' ',
     302                                          subsequent_indent = ' '
     303                                  )
     304                          if self.__pat['pk_emergency_contact'] is not None:
     305                                  contact = self.__pat.emergency_contact_in_database
     306                                  self.__root_tooltip += ' %s\n' % contact['description_gender']
     307                  self.__root_tooltip = self.__root_tooltip.strip('\n')
     308                  if self.__root_tooltip == '':
     309                          self.__root_tooltip = ' '
     310
     311                  return root_item
     312
     313          #--------------------------------------------------------
     314 -        def __update_text_for_selected_node(self):
     315                  """Displays information for the selected tree node."""
     316
     317                  if self.__soap_display is None:
     318                          return
     319
     320                  self.__soap_display.Clear()
     321                  self.__img_display.clear()
     322
     323                  if self.__curr_node is None:
     324                          return
     325
     326                  if not self.__curr_node.IsOk():
     327                          return
     328
     329                  try:
     330                          node_data = self.GetItemData(self.__curr_node)
     331                  except wx.wxAssertionError:
     332                          node_data = None                # fake a root node
     333                          _log.exception('unfathomable self.GetItemData() problem occurred, faking root node')
     334                          _log.error('real node: %s', self.__curr_node)
     335                          _log.error('node.IsOk(): %s', self.__curr_node.IsOk())          # already survived this further up
     336                          _log.error('is root node: %s', self.__curr_node == self.GetRootItem())
     337                          _log.error('node attributes: %s', dir(self.__curr_node))
     338                          gmLog2.log_stack_trace()
     339
     340                  doc_folder = self.__pat.get_document_folder()
     341
     342                  if isinstance(node_data, gmEMRStructItems.cHealthIssue):
     343                          self.__cb__enable_display_mode_selection(True)
     344                          txt = 'invalid SOAP display mode [%s]' % self.__soap_display_mode
     345                          if self.__soap_display_mode == 'details':
     346                                  txt = node_data.format(left_margin = 1, patient = self.__pat)
     347                                  font = self.__soap_display_prop_font
     348                          if self.__soap_display_mode == 'journal':
     349                                  txt = node_data.format_as_journal(left_margin = 1)
     350                                  font = self.__soap_display_prop_font
     351                          if self.__soap_display_mode == 'revisions':
     352                                  txt = node_data.formatted_revision_history
     353                                  font = self.__soap_display_mono_font
     354                          epis = node_data.episodes
     355                          if len(epis) > 0:
     356                                  self.__img_display.refresh (
     357                                          document_folder = doc_folder,
     358                                          episodes = [ epi['pk_episode'] for epi in epis ],
     359                                          do_async = True
     360                                  )
     361                          self.__soap_display.SetFont(font)
     362                          self.__soap_display.WriteText(txt)
     363                          self.__soap_display.ShowPosition(0)
     364                          return
     365
     366                  # unassociated episodes         # FIXME: turn into real dummy issue
     367                  if isinstance(node_data, type({})):
     368                          self.__cb__enable_display_mode_selection(True)
     369                          if self.__soap_display_mode == 'details':
     370                                  txt = _('Pool of unassociated episodes "%s":\n') % node_data['description']
     371                                  epis = self.__pat.emr.get_episodes(unlinked_only = True, order_by = 'episode_open DESC, description')
     372                                  if len(epis) > 0:
     373                                          txt += '\n'
     374                                  for epi in epis:
     375                                          txt += epi.format (
     376                                                  left_margin = 1,
     377                                                  patient = self.__pat,
     378                                                  with_summary = True,
     379                                                  with_codes = False,
     380                                                  with_encounters = False,
     381                                                  with_documents = False,
     382                                                  with_hospital_stays = False,
     383                                                  with_procedures = False,
     384                                                  with_family_history = False,
     385                                                  with_tests = False,
     386                                                  with_vaccinations = False,
     387                                                  with_health_issue = False
     388                                          )
     389                                          txt += '\n'
     390                          else:
     391                                  epis = self.__pat.emr.get_episodes(unlinked_only = True, order_by = 'episode_open DESC, description')
     392                                  txt = ''
     393                                  if len(epis) > 0:
     394                                          txt += _(' Listing of unassociated episodes\n')
     395                                  for epi in epis:
     396                                          txt += ' %s\n' % (gmTools.u_box_horiz_4dashes * 60)
     397                                          txt += epi.format (
     398                                                  left_margin = 1,
     399                                                  patient = self.__pat,
     400                                                  with_summary = False,
     401                                                  with_codes = False,
     402                                                  with_encounters = False,
     403                                                  with_documents = False,
     404                                                  with_hospital_stays = False,
     405                                                  with_procedures = False,
     406                                                  with_family_history = False,
     407                                                  with_tests = False,
     408                                                  with_vaccinations = False,
     409                                                  with_health_issue = False
     410                                          )
     411                                          txt += '\n'
     412                                          txt += epi.format_as_journal(left_margin = 2)
     413                          self.__soap_display.SetFont(self.__soap_display_prop_font)
     414                          self.__soap_display.WriteText(txt)
     415                          self.__soap_display.ShowPosition(0)
     416                          return
     417
     418                  if isinstance(node_data, gmEMRStructItems.cEpisode):
     419                          self.__cb__enable_display_mode_selection(True)
     420                          txt = 'invalid SOAP display mode [%s]' % self.__soap_display_mode
     421                          if self.__soap_display_mode == 'details':
     422                                  txt = node_data.format(left_margin = 1, patient = self.__pat)
     423                                  font = self.__soap_display_prop_font
     424                          if self.__soap_display_mode == 'journal':
     425                                  txt = node_data.format_as_journal(left_margin = 1)
     426                                  font = self.__soap_display_prop_font
     427                          if self.__soap_display_mode == 'revisions':
     428                                  txt = node_data.formatted_revision_history
     429                                  font = self.__soap_display_mono_font
     430                          self.__img_display.refresh (
     431                                  document_folder = doc_folder,
     432                                  episodes = [ node_data['pk_episode'] ]
     433                          )
     434                          self.__soap_display.SetFont(font)
     435                          self.__soap_display.WriteText(txt)
     436                          self.__soap_display.ShowPosition(0)
     437                          return
     438
     439                  if isinstance(node_data, gmEMRStructItems.cEncounter):
     440                          self.__cb__enable_display_mode_selection(True)
     441                          epi = self.GetItemData(self.GetItemParent(self.__curr_node))
     442                          if self.__soap_display_mode == 'revisions':
     443                                  txt = node_data.formatted_revision_history
     444                                  font = self.__soap_display_mono_font
     445                          else:
     446                                  txt = node_data.format (
     447                                          episodes = [epi['pk_episode']],
     448                                          with_soap = True,
     449                                          left_margin = 1,
     450                                          patient = self.__pat,
     451                                          with_co_encountlet_hints = True
     452                                  )
     453                                  font = self.__soap_display_prop_font
     454                          self.__img_display.refresh (
     455                                  document_folder = doc_folder,
     456                                  episodes = [ epi['pk_episode'] ],
     457                                  encounter = node_data['pk_encounter']
     458                          )
     459                          self.__soap_display.SetFont(font)
     460                          self.__soap_display.WriteText(txt)
     461                          self.__soap_display.ShowPosition(0)
     462                          return
     463
     464                  # root node == EMR level
     465                  self.__cb__enable_display_mode_selection(True)
     466                  if self.__soap_display_mode == 'details':
     467                          emr = self.__pat.emr
     468                          txt = emr.format_summary()
     469                  else:
     470                          txt = self.__pat.emr.format_as_journal(left_margin = 1, patient = self.__pat)
     471                  self.__soap_display.SetFont(self.__soap_display_prop_font)
     472                  self.__soap_display.WriteText(txt)
     473                  self.__soap_display.ShowPosition(0)
     474
     475          #--------------------------------------------------------
     476 -        def __make_popup_menus(self):
     477
     478                  # - root node
     479                  self.__root_context_popup = wx.Menu(title = _('EMR Actions:'))
     480                  item = self.__root_context_popup.Append(-1, _('Print EMR'))
     481                  self.Bind(wx.EVT_MENU, self.__print_emr, item)
     482                  item = self.__root_context_popup.Append(-1, _('Create health issue'))
     483                  self.Bind(wx.EVT_MENU, self.__create_issue, item)
     484                  item = self.__root_context_popup.Append(-1, _('Create episode'))
     485                  self.Bind(wx.EVT_MENU, self.__create_episode, item)
     486                  item = self.__root_context_popup.Append(-1, _('Create progress note'))
     487                  self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
     488                  item = self.__root_context_popup.Append(-1, _('Manage allergies'))
     489                  self.Bind(wx.EVT_MENU, self.__document_allergy, item)
     490                  item = self.__root_context_popup.Append(-1, _('Manage family history'))
     491                  self.Bind(wx.EVT_MENU, self.__manage_family_history, item)
     492                  item = self.__root_context_popup.Append(-1, _('Manage hospitalizations'))
     493                  self.Bind(wx.EVT_MENU, self.__manage_hospital_stays, item)
     494                  item = self.__root_context_popup.Append(-1, _('Manage occupation'))
     495                  self.Bind(wx.EVT_MENU, self.__manage_occupation, item)
     496                  item = self.__root_context_popup.Append(-1, _('Manage procedures'))
     497                  self.Bind(wx.EVT_MENU, self.__manage_procedures, item)
     498                  item = self.__root_context_popup.Append(-1, _('Manage vaccinations'))
     499                  self.Bind(wx.EVT_MENU, self.__manage_vaccinations, item)
     500
     501                  self.__root_context_popup.AppendSeparator()
     502
     503                  # expand tree
     504                  expand_menu = wx.Menu()
     505                  self.__root_context_popup.Append(wx.NewId(), _('Open EMR to ...'), expand_menu)
     506                  item = expand_menu.Append(-1, _('... issue level'))
     507                  self.Bind(wx.EVT_MENU, self.__expand_to_issue_level, item)
     508                  item = expand_menu.Append(-1, _('... episode level'))
     509                  self.Bind(wx.EVT_MENU, self.__expand_to_episode_level, item)
     510                  item = expand_menu.Append(-1, _('... encounter level'))
     511                  self.Bind(wx.EVT_MENU, self.__expand_to_encounter_level, item)
     512
     513                  # - health issues
     514                  self.__issue_context_popup = wx.Menu(title = _('Health Issue Actions:'))
     515                  item = self.__issue_context_popup.Append(-1, _('Edit details'))
     516                  self.Bind(wx.EVT_MENU, self.__edit_issue, item)
     517                  item = self.__issue_context_popup.Append(-1, _('Delete'))
     518                  self.Bind(wx.EVT_MENU, self.__delete_issue, item)
     519                  self.__issue_context_popup.AppendSeparator()
     520                  item = self.__issue_context_popup.Append(-1, _('Open to encounter level'))
     521                  self.Bind(wx.EVT_MENU, self.__expand_issue_to_encounter_level, item)
     522                  # print " attach issue to another patient"
     523                  # print " move all episodes to another issue"
     524                  item = self.__issue_context_popup.Append(-1, _('Create progress note'))
     525                  self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
     526
     527                  # - episodes
     528                  self.__epi_context_popup = wx.Menu(title = _('Episode Actions:'))
     529                  item = self.__epi_context_popup.Append(-1, _('Toggle ongoing/closed'))
     530                  self.Bind(wx.EVT_MENU, self.__toggle_episode_open_close, item)
     531                  item = self.__epi_context_popup.Append(-1, _('Edit details'))
     532                  self.Bind(wx.EVT_MENU, self.__edit_episode, item)
     533                  item = self.__epi_context_popup.Append(-1, _('Delete'))
     534                  self.Bind(wx.EVT_MENU, self.__delete_episode, item)
     535                  item = self.__epi_context_popup.Append(-1, _('Promote'))
     536                  self.Bind(wx.EVT_MENU, self.__promote_episode_to_issue, item)
     537                  item = self.__epi_context_popup.Append(-1, _('Create progress note'))
     538                  self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
     539                  item = self.__epi_context_popup.Append(-1, _('Move encounters'))
     540                  self.Bind(wx.EVT_MENU, self.__move_encounters, item)
     541
     542                  # - encounters
     543                  self.__enc_context_popup = wx.Menu(title = _('Encounter Actions:'))
     544                  item = self.__enc_context_popup.Append(-1, _('Move data to another episode'))
     545                  self.Bind(wx.EVT_MENU, self.__relink_encounter_data2episode, item)
     546                  item = self.__enc_context_popup.Append(-1, _('Edit details'))
     547                  self.Bind(wx.EVT_MENU, self.__edit_encounter_details, item)
     548                  # would require pre-configurable save-under which we don't have
     549                  #item = self.__enc_context_popup.Append(-1, _('Create progress note'))
     550                  #self.Bind(wx.EVT_MENU, self.__create_soap_editor, item)
     551                  item = self.__enc_context_popup.Append(-1, _('Edit progress notes'))
     552                  self.Bind(wx.EVT_MENU, self.__edit_progress_notes, item)
     553                  item = self.__enc_context_popup.Append(-1, _('Move progress notes'))
     554                  self.Bind(wx.EVT_MENU, self.__move_progress_notes, item)
     555                  item = self.__enc_context_popup.Append(-1, _('Export for Medistar'))
     556                  self.Bind(wx.EVT_MENU, self.__export_encounter_for_medistar, item)
     557
     558          #--------------------------------------------------------
     559 -        def __handle_root_context(self, pos=wx.DefaultPosition):
     560                  self.PopupMenu(self.__root_context_popup, pos)
     561
     562          #--------------------------------------------------------
     563 -        def __handle_issue_context(self, pos=wx.DefaultPosition):
     564                  self.PopupMenu(self.__issue_context_popup, pos)
     565
     566          #--------------------------------------------------------
     567 -        def __handle_episode_context(self, pos=wx.DefaultPosition):
     568                  self.PopupMenu(self.__epi_context_popup, pos)
     569
     570          #--------------------------------------------------------
     571 -        def __handle_encounter_context(self, pos=wx.DefaultPosition):
     572                  self.PopupMenu(self.__enc_context_popup, pos)
     573
     574          #--------------------------------------------------------
     575          # episode level
     576          #--------------------------------------------------------
     577 -        def __move_encounters(self, event):
     578                  episode = self.GetItemData(self.__curr_node)
     579
     580                  gmNarrativeWorkflows.move_progress_notes_to_another_encounter (
     581                          parent = self,
     582                          episodes = [episode['pk_episode']],
     583                          move_all = True
     584                  )
     585
     586          #--------------------------------------------------------
     587 -        def __toggle_episode_open_close(self, event):
     588                  self.__curr_node_data['episode_open'] = not self.__curr_node_data['episode_open']
     589                  self.__curr_node_data.save()
     590
     591          #--------------------------------------------------------
     592 -        def __edit_episode(self, event):
     593                  gmEMRStructWidgets.edit_episode(parent = self, episode = self.__curr_node_data)
     594
     595          #--------------------------------------------------------
     596 -        def __promote_episode_to_issue(self, evt):
     597                  gmEMRStructWidgets.promote_episode_to_issue(parent=self, episode = self.__curr_node_data, emr = self.__pat.emr)
     598
     599          #--------------------------------------------------------
     600 -        def __delete_episode(self, event):
     601                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
     602                          parent = self,
     603                          id = -1,
     604                          caption = _('Deleting episode'),
     605                          button_defs = [
     606                                  {'label': _('Yes, delete'), 'tooltip': _('Delete the episode if possible (it must be completely empty).')},
     607                                  {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the episode.')}
     608                          ],
     609                          question = _(
     610                                  'Are you sure you want to delete this episode ?\n'
     611                                  '\n'
     612                                  ' "%s"\n'
     613                          ) % self.__curr_node_data['description']
     614                  )
     615                  result = dlg.ShowModal()
     616                  if result != wx.ID_YES:
     617                          return
     618
     619                  if not gmEMRStructItems.delete_episode(episode = self.__curr_node_data):
     620                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete episode. There is still clinical data recorded for it.'))
     621
     622          #--------------------------------------------------------
     623 -        def __expand_episode_node(self, episode_node=None):
     624                  self.DeleteChildren(episode_node)
     625
     626                  emr = self.__pat.emr
     627                  epi = self.GetItemData(episode_node)
     628                  encounters = emr.get_encounters(episodes = [epi['pk_episode']], skip_empty = True)
     629                  if len(encounters) == 0:
     630                          self.SetItemHasChildren(episode_node, False)
     631                          return
     632
     633                  self.SetItemHasChildren(episode_node, True)
     634
     635                  for enc in encounters:
     636                          label = '%s: %s' % (
     637                                  enc['started'].strftime('%Y-%m-%d'),
     638                                  gmTools.unwrap (
     639                                          gmTools.coalesce (
     640                                                  gmTools.coalesce (
     641                                                          gmTools.coalesce (
     642                                                                  enc.get_latest_soap (                           # soAp
     643                                                                          soap_cat = 'a',
     644                                                                          episode = epi['pk_episode']
     645                                                                  ),
     646                                                                  enc['assessment_of_encounter']          # or AOE
     647                                                          ),
     648                                                          enc['reason_for_encounter']                             # or RFE
     649                                                  ),
     650                                                  enc['l10n_type']                                                        # or type
     651                                          ),
     652                                          max_length = 40
     653                                  )
     654                          )
     655                          encounter_node = self.AppendItem(episode_node, label)
     656                          self.SetItemData(encounter_node, enc)
     657                          # we don't expand encounter nodes (what for ?)
     658                          self.SetItemHasChildren(encounter_node, False)
     659
     660                  self.SortChildren(episode_node)
     661
     662          #--------------------------------------------------------
     663          # encounter level
     664          #--------------------------------------------------------
     665 -        def __move_progress_notes(self, evt):
     666                  encounter = self.GetItemData(self.__curr_node)
     667                  node_parent = self.GetItemParent(self.__curr_node)
     668                  episode = self.GetItemData(node_parent)
     669
     670                  gmNarrativeWorkflows.move_progress_notes_to_another_encounter (
     671                          parent = self,
     672                          encounters = [encounter['pk_encounter']],
     673                          episodes = [episode['pk_episode']]
     674                  )
     675
     676          #--------------------------------------------------------
     677 -        def __edit_progress_notes(self, event):
     678                  encounter = self.GetItemData(self.__curr_node)
     679                  node_parent = self.GetItemParent(self.__curr_node)
     680                  episode = self.GetItemData(node_parent)
     681
     682                  gmNarrativeWorkflows.manage_progress_notes (
     683                          parent = self,
     684                          encounters = [encounter['pk_encounter']],
     685                          episodes = [episode['pk_episode']]
     686                  )
     687
     688          #--------------------------------------------------------
     689 -        def __edit_encounter_details(self, event):
     690                  node_data = self.GetItemData(self.__curr_node)
     691                  gmEncounterWidgets.edit_encounter(parent = self, encounter = node_data)
     692                  self.__populate_tree()
     693
     694          #--------------------------------------------------------
     695 -        def __relink_encounter_data2episode(self, event):
     696
     697                  node_parent = self.GetItemParent(self.__curr_node)
     698                  owning_episode = self.GetItemData(node_parent)
     699
     700                  episode_selector = gmNarrativeWidgets.cMoveNarrativeDlg (
     701                          self,
     702                          -1,
     703                          episode = owning_episode,
     704                          encounter = self.__curr_node_data
     705                  )
     706
     707                  result = episode_selector.ShowModal()
     708                  episode_selector.Destroy()
     709
     710                  if result == wx.ID_YES:
     711                          self.__populate_tree()
     712
     713          #--------------------------------------------------------
     714          # issue level
     715          #--------------------------------------------------------
     716 -        def __edit_issue(self, event):
     717                  gmEMRStructWidgets.edit_health_issue(parent = self, issue = self.__curr_node_data)
     718
     719          #--------------------------------------------------------
     720 -        def __delete_issue(self, event):
     721                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
     722                          parent = self,
     723                          id = -1,
     724                          caption = _('Deleting health issue'),
     725                          button_defs = [
     726                                  {'label': _('Yes, delete'), 'tooltip': _('Delete the health issue if possible (it must be completely empty).')},
     727                                  {'label': _('No, cancel'), 'tooltip': _('Cancel and do NOT delete the health issue.')}
     728                          ],
     729                          question = _(
     730                                  'Are you sure you want to delete this health issue ?\n'
     731                                  '\n'
     732                                  ' "%s"\n'
     733                          ) % self.__curr_node_data['description']
     734                  )
     735                  result = dlg.ShowModal()
     736                  if result != wx.ID_YES:
     737                          dlg.Destroy()
     738                          return
     739
     740                  dlg.Destroy()
     741
     742                  if not gmEMRStructItems.delete_health_issue(health_issue = self.__curr_node_data):
     743                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete health issue. There is still clinical data recorded for it.'))
     744
     745          #--------------------------------------------------------
     746 -        def __expand_issue_to_encounter_level(self, evt):
     747
     748                  if not self.__curr_node.IsOk():
     749                          return
     750
     751                  self.Expand(self.__curr_node)
     752
     753                  epi, epi_cookie = self.GetFirstChild(self.__curr_node)
     754                  while epi.IsOk():
     755                          self.Expand(epi)
     756                          epi, epi_cookie = self.GetNextChild(self.__curr_node, epi_cookie)
     757
     758          #--------------------------------------------------------
     759 -        def __expand_issue_node(self, issue_node=None):
     760                  self.DeleteChildren(issue_node)
     761
     762                  issue = self.GetItemData(issue_node)
     763                  episodes = self.__pat.emr.get_episodes(issues = [issue['pk_health_issue']])
     764                  if len(episodes) == 0:
     765                          self.SetItemHasChildren(issue_node, False)
     766                          return
     767
     768                  self.SetItemHasChildren(issue_node, True)
     769
     770                  for episode in episodes:
     771                          range_str, range_str_verb, duration_str = episode.formatted_clinical_duration
     772                          episode_node =  self.AppendItem(issue_node, '%s (%s)' % (
     773                                  episode['description'],
     774                                  range_str
     775                          ))
     776                          self.SetItemData(episode_node, episode)
     777                          # assume children so we can try to expand it
     778                          self.SetItemHasChildren(episode_node, True)
     779
     780                  self.SortChildren(issue_node)
     781
     782          #--------------------------------------------------------
     783 -        def __expand_pseudo_issue_node(self, fake_issue_node=None):
     784                  self.DeleteChildren(fake_issue_node)
     785
     786                  episodes = self.__pat.emr.unlinked_episodes
     787                  if len(episodes) == 0:
     788                          self.SetItemHasChildren(fake_issue_node, False)
     789                          return
     790
     791                  self.SetItemHasChildren(fake_issue_node, True)
     792
     793                  for episode in episodes:
     794                          range_str, range_str_verb, duration_str = episode.formatted_clinical_duration
     795                          episode_node =  self.AppendItem(fake_issue_node, '%s (%s)' % (
     796                                  episode['description'],
     797                                  range_str
     798                          ))
     799                          self.SetItemData(episode_node, episode)
     800                          if episode['episode_open']:
     801                                  self.SetItemBold(fake_issue_node, True)
     802                          # assume children so we can try to expand it
     803                          self.SetItemHasChildren(episode_node, True)
     804
     805                  self.SortChildren(fake_issue_node)
     806
     807          #--------------------------------------------------------
     808          # EMR level
     809          #--------------------------------------------------------
     810 -        def __print_emr(self, event):
     811                  gmFormWidgets.print_doc_from_template(parent = self)
     812
     813          #--------------------------------------------------------
     814 -        def __create_issue(self, event):
     815                  gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
     816
     817          #--------------------------------------------------------
     818 -        def __create_episode(self, event):
     819                  gmEMRStructWidgets.edit_episode(parent = self, episode = None)
     820
     821          #--------------------------------------------------------
     822 -        def __create_soap_editor(self, event):
     823                  self.__cb__select_edit_mode(True)
     824                  self.__cb__add_soap_editor(problem = self.__curr_node_data, allow_same_problem = False)
     825
     826          #--------------------------------------------------------
     827 -        def __document_allergy(self, event):
     828                  dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1)
     829                  # FIXME: use signal and use node level update
     830                  if dlg.ShowModal() == wx.ID_OK:
     831                          self.__expanded_nodes = self.ExpansionState
     832                          self.__populate_tree()
     833                  dlg.Destroy()
     834                  return
     835
     836          #--------------------------------------------------------
     837 -        def __manage_procedures(self, event):
     838                  gmProcedureWidgets.manage_performed_procedures(parent = self)
     839
     840          #--------------------------------------------------------
     841 -        def __manage_family_history(self, event):
     842                  gmFamilyHistoryWidgets.manage_family_history(parent = self)
     843
     844          #--------------------------------------------------------
     845 -        def __manage_hospital_stays(self, event):
     846                  gmHospitalStayWidgets.manage_hospital_stays(parent = self)
     847
     848          #--------------------------------------------------------
     849 -        def __manage_occupation(self, event):
     850                  gmDemographicsWidgets.edit_occupation()
     851
     852          #--------------------------------------------------------
     853 -        def __manage_vaccinations(self, event):
     854                  gmVaccWidgets.manage_vaccinations(parent = self)
     855
     856          #--------------------------------------------------------
     857 -        def __expand_to_issue_level(self, evt):
     858
     859                  root_item = self.GetRootItem()
     860
     861                  if not root_item.IsOk():
     862                          return
     863
     864                  self.Expand(root_item)
     865
     866                  # collapse episodes and issues
     867                  issue, issue_cookie = self.GetFirstChild(root_item)
     868                  while issue.IsOk():
     869                          self.Collapse(issue)
     870                          epi, epi_cookie = self.GetFirstChild(issue)
     871                          while epi.IsOk():
     872                                  self.Collapse(epi)
     873                                  epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
     874                          issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
     875
     876          #--------------------------------------------------------
     877 -        def __expand_to_episode_level(self, evt):
     878
     879                  root_item = self.GetRootItem()
     880
     881                  if not root_item.IsOk():
     882                          return
     883
     884                  self.Expand(root_item)
     885
     886                  # collapse episodes, expand issues
     887                  issue, issue_cookie = self.GetFirstChild(root_item)
     888                  while issue.IsOk():
     889                          self.Expand(issue)
     890                          epi, epi_cookie = self.GetFirstChild(issue)
     891                          while epi.IsOk():
     892                                  self.Collapse(epi)
     893                                  epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
     894                          issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
     895
     896          #--------------------------------------------------------
     897 -        def __expand_to_encounter_level(self, evt):
     898
     899                  root_item = self.GetRootItem()
     900
     901                  if not root_item.IsOk():
     902                          return
     903
     904                  self.Expand(root_item)
     905
     906                  # collapse episodes, expand issues
     907                  issue, issue_cookie = self.GetFirstChild(root_item)
     908                  while issue.IsOk():
     909                          self.Expand(issue)
     910                          epi, epi_cookie = self.GetFirstChild(issue)
     911                          while epi.IsOk():
     912                                  self.Expand(epi)
     913                                  epi, epi_cookie = self.GetNextChild(issue, epi_cookie)
     914                          issue, issue_cookie = self.GetNextChild(root_item, issue_cookie)
     915
     916          #--------------------------------------------------------
     917 -        def __export_encounter_for_medistar(self, evt):
     918                  gmNarrativeWorkflows.export_narrative_for_medistar_import (
     919                          parent = self,
     920                          soap_cats = 'soapu',
     921                          encounter = self.__curr_node_data
     922                  )
     923          #--------------------------------------------------------
     924 -        def __expand_root_node(self):
     925                  root_node = self.GetRootItem()
     926                  self.DeleteChildren(root_node)
     927
     928                  issues = [{
     929                          'description': _('Unattributed episodes'),
     930                          'laterality': None,
     931                          'diagnostic_certainty_classification': None,
     932                          'has_open_episode': False,
     933                          'pk_health_issue': None
     934                  }]
     935                  issues.extend(self.__pat.emr.health_issues)
     936                  for issue in issues:
     937                          issue_node =  self.AppendItem(root_node, '%s%s%s' % (
     938                                  issue['description'],
     939                                  gmTools.coalesce(issue['laterality'], '', ' [%s]', none_equivalents = [None, 'na']),
     940                                  gmTools.coalesce(issue['diagnostic_certainty_classification'], '', ' [%s]')
     941                          ))
     942                          self.SetItemBold(issue_node, issue['has_open_episode'])
     943                          self.SetItemData(issue_node, issue)
     944                          # fake it so we can expand it
     945                          self.SetItemHasChildren(issue_node, True)
     946
     947                  self.SetItemHasChildren(root_node, (len(issues) != 0))
     948                  self.SortChildren(root_node)
     949
     950          #--------------------------------------------------------
     951          # event handlers
     952          #--------------------------------------------------------
     953 -        def _on_narrative_mod_db(self, *args, **kwargs):
     954                  self.__update_text_for_selected_node()
     955
     956          #--------------------------------------------------------
     957 -        def _on_episode_mod_db(self, *args, **kwargs):
     958                  self.__expanded_nodes = self.ExpansionState
     959                  self.__populate_tree()
     960
     961          #--------------------------------------------------------
     962 -        def _on_issue_mod_db(self, *args, **kwargs):
     963                  self.__expanded_nodes = self.ExpansionState
     964                  self.__populate_tree()
     965
     966          #--------------------------------------------------------
     967 -        def _on_tree_item_expanding(self, event):
     968                  event.Skip()
     969
     970                  node = event.GetItem()
     971                  if node == self.GetRootItem():
     972                          self.__expand_root_node()
     973                          return
     974
     975                  node_data = self.GetItemData(node)
     976
     977                  if isinstance(node_data, gmEMRStructItems.cHealthIssue):
     978                          self.__expand_issue_node(issue_node = node)
     979                          return
     980
     981                  if isinstance(node_data, gmEMRStructItems.cEpisode):
     982                          self.__expand_episode_node(episode_node = node)
     983                          return
     984
     985                  # pseudo node "free-standing episodes"
     986                  if type(node_data) == type({}):
     987                          self.__expand_pseudo_issue_node(fake_issue_node = node)
     988                          return
     989
     990                  # encounter nodes do not need expanding
     991                  #if isinstance(node_data, gmEMRStructItems.cEncounter):
     992
     993          #--------------------------------------------------------
     994 -        def _on_tree_item_selected(self, event):
     995                  sel_item = event.GetItem()
     996                  self.__curr_node = sel_item
     997                  self.__update_text_for_selected_node()
     998                  return True
     999
    1000  #       #--------------------------------------------------------
    1001  #       def _on_mouse_motion(self, event):
    1002  #
    1003  #               cursor_pos = (event.GetX(), event.GetY())
    1004  #
    1005  #               self.SetToolTip(u'')
    1006  #
    1007  #               if cursor_pos != self._old_cursor_pos:
    1008  #                       self._old_cursor_pos = cursor_pos
    1009  #                       (item, flags) = self.HitTest(cursor_pos)
    1010  #                       #if flags != wx.TREE_HITTEST_NOWHERE:
    1011  #                       if flags == wx.TREE_HITTEST_ONITEMLABEL:
    1012  #                               data = self.GetItemData(item)
    1013  #
    1014  #                               if not isinstance(data, gmEMRStructItems.cEncounter):
    1015  #                                       return
    1016  #
    1017  #                               self.SetToolTip(u'%s  %s  %s - %s\n\nRFE: %s\nAOE: %s' % (
    1018  #                                       gmDateTime.pydt_strftime(data['started'], '%Y %b %d'),
    1019  #                                       data['l10n_type'],
    1020  #                                       data['started'].strftime('%H:%m'),
    1021  #                                       data['last_affirmed'].strftime('%H:%m'),
    1022  #                                       gmTools.coalesce(data['reason_for_encounter'], u''),
    1023  #                                       gmTools.coalesce(data['assessment_of_encounter'], u'')
    1024  #                               ))
    1025          #--------------------------------------------------------
    1026 -        def _on_tree_item_gettooltip(self, event):
    1027
    1028                  item = event.GetItem()
    1029
    1030                  if not item.IsOk():
    1031                          event.SetToolTip(' ')
    1032                          return
    1033
    1034                  data = self.GetItemData(item)
    1035
    1036                  if isinstance(data, gmEMRStructItems.cEncounter):
    1037                          tt = '%s  %s  %s - %s\n' % (
    1038                                  gmDateTime.pydt_strftime(data['started'], '%Y %b %d'),
    1039                                  data['l10n_type'],
    1040                                  data['started'].strftime('%H:%M'),
    1041                                  data['last_affirmed'].strftime('%H:%M')
    1042                          )
    1043                          if data['reason_for_encounter'] is not None:
    1044                                  tt += '\n'
    1045                                  tt += _('RFE: %s') % data['reason_for_encounter']
    1046                                  if len(data['pk_generic_codes_rfe']) > 0:
    1047                                          for code in data.generic_codes_rfe:
    1048                                                  tt += '\n %s: %s%s%s\n  (%s %s)' % (
    1049                                                          code['code'],
    1050                                                          gmTools.u_left_double_angle_quote,
    1051                                                          code['term'],
    1052                                                          gmTools.u_right_double_angle_quote,
    1053                                                          code['name_short'],
    1054                                                          code['version']
    1055                                                  )
    1056                          if data['assessment_of_encounter'] is not None:
    1057                                  tt += '\n'
    1058                                  tt += _('AOE: %s') % data['assessment_of_encounter']
    1059                                  if len(data['pk_generic_codes_aoe']) > 0:
    1060                                          for code in data.generic_codes_aoe:
    1061                                                  tt += '\n %s: %s%s%s\n  (%s %s)' % (
    1062                                                          code['code'],
    1063                                                          gmTools.u_left_double_angle_quote,
    1064                                                          code['term'],
    1065                                                          gmTools.u_right_double_angle_quote,
    1066                                                          code['name_short'],
    1067                                                          code['version']
    1068                                                  )
    1069
    1070                  elif isinstance(data, gmEMRStructItems.cEpisode):
    1071                          tt = ''
    1072                          tt += gmTools.bool2subst (
    1073                                  (data['diagnostic_certainty_classification'] is not None),
    1074                                  data.diagnostic_certainty_description + '\n\n',
    1075                                  ''
    1076                          )
    1077                          tt += gmTools.bool2subst (
    1078                                  data['episode_open'],
    1079                                  _('ongoing episode'),
    1080                                  _('closed episode'),
    1081                                  'error: episode state is None'
    1082                          ) + '\n'
    1083                          tt += gmTools.coalesce(data['summary'], '', '\n%s')
    1084                          if len(data['pk_generic_codes']) > 0:
    1085                                  tt += '\n'
    1086                                  for code in data.generic_codes:
    1087                                          tt += '%s: %s%s%s\n  (%s %s)\n' % (
    1088                                                  code['code'],
    1089                                                  gmTools.u_left_double_angle_quote,
    1090                                                  code['term'],
    1091                                                  gmTools.u_right_double_angle_quote,
    1092                                                  code['name_short'],
    1093                                                  code['version']
    1094                                          )
    1095
    1096                          tt = tt.strip('\n')
    1097                          if tt == '':
    1098                                  tt = ' '
    1099
    1100                  elif isinstance(data, gmEMRStructItems.cHealthIssue):
    1101                          tt = ''
    1102                          tt += gmTools.bool2subst(data['is_confidential'], _('*** CONFIDENTIAL ***\n\n'), '')
    1103                          tt += gmTools.bool2subst (
    1104                                  (data['diagnostic_certainty_classification'] is not None),
    1105                                  data.diagnostic_certainty_description + '\n',
    1106                                  ''
    1107                          )
    1108                          tt += gmTools.bool2subst (
    1109                                  (data['laterality'] not in [None, 'na']),
    1110                                  data.laterality_description + '\n',
    1111                                  ''
    1112                          )
    1113                          # noted_at_age is too costly
    1114                          tt += gmTools.bool2subst(data['is_active'], _('active') + '\n', '')
    1115                          tt += gmTools.bool2subst(data['clinically_relevant'], _('clinically relevant') + '\n', '')
    1116                          tt += gmTools.bool2subst(data['is_cause_of_death'], _('contributed to death') + '\n', '')
    1117                          tt += gmTools.coalesce(data['grouping'], '\n', _('Grouping: %s') + '\n')
    1118                          tt += gmTools.coalesce(data['summary'], '', '\n%s')
    1119                          if len(data['pk_generic_codes']) > 0:
    1120                                  tt += '\n'
    1121                                  for code in data.generic_codes:
    1122                                          tt += '%s: %s%s%s\n  (%s %s)\n' % (
    1123                                                  code['code'],
    1124                                                  gmTools.u_left_double_angle_quote,
    1125                                                  code['term'],
    1126                                                  gmTools.u_right_double_angle_quote,
    1127                                                  code['name_short'],
    1128                                                  code['version']
    1129                                          )
    1130
    1131                          tt = tt.strip('\n')
    1132                          if tt == '':
    1133                                  tt = ' '
    1134
    1135                  else:
    1136                          tt = self.__root_tooltip
    1137
    1138                  event.SetToolTip(tt)
    1139
    1140                  # doing this prevents the tooltip from showing at all
    1141                  #event.Skip()
    1142
    1143  #widgetXY.GetToolTip().Enable(False)
    1144  #
    1145  #seems to work, supposing the tooltip is actually set for the widget,
    1146  #otherwise a test would be needed
    1147  #if widgetXY.GetToolTip():
    1148  #    widgetXY.GetToolTip().Enable(False)
    1149
    1150          #--------------------------------------------------------
    1151 -        def _on_tree_item_right_clicked(self, event):
    1152                  """Right button clicked: display the popup for the tree"""
    1153
    1154                  node = event.GetItem()
    1155                  self.SelectItem(node)
    1156                  self.__curr_node_data = self.GetItemData(node)
    1157                  self.__curr_node = node
    1158
    1159                  pos = wx.DefaultPosition
    1160                  if isinstance(self.__curr_node_data, gmEMRStructItems.cHealthIssue):
    1161                          self.__handle_issue_context(pos=pos)
    1162                  elif isinstance(self.__curr_node_data, gmEMRStructItems.cEpisode):
    1163                          self.__handle_episode_context(pos=pos)
    1164                  elif isinstance(self.__curr_node_data, gmEMRStructItems.cEncounter):
    1165                          self.__handle_encounter_context(pos=pos)
    1166                  elif node == self.GetRootItem():
    1167                          self.__handle_root_context()
    1168                  elif type(self.__curr_node_data) == type({}):
    1169                          # ignore pseudo node "free-standing episodes"
    1170                          pass
    1171                  else:
    1172                          print("error: unknown node type, no popup menu")
    1173                  event.Skip()
    1174
    1175          #--------------------------------------------------------
    1176 -        def OnCompareItems (self, node1=None, node2=None):
    1177                  """Used in sorting items.
    1178
    1179                  -1: 1 < 2
    1180                   0: 1 = 2
    1181                   1: 1 > 2
    1182                  """
    1183                  # FIXME: implement sort modes, chron, reverse cron, by regex, etc
    1184
    1185                  if not node1:
    1186                          _log.debug('invalid node 1')
    1187                          return 0
    1188                  if not node2:
    1189                          _log.debug('invalid node 2')
    1190                          return 0
    1191
    1192                  if not node1.IsOk():
    1193                          _log.debug('invalid node 1')
    1194                          return 0
    1195                  if not node2.IsOk():
    1196                          _log.debug('invalid node 2')
    1197                          return 0
    1198
    1199                  item1 = self.GetItemData(node1)
    1200                  item2 = self.GetItemData(node2)
    1201
    1202                  # dummy health issue always on top
    1203                  if isinstance(item1, type({})):
    1204                          return -1
    1205                  if isinstance(item2, type({})):
    1206                          return 1
    1207
    1208                  # encounters: reverse chronologically
    1209                  if isinstance(item1, gmEMRStructItems.cEncounter):
    1210                          if item1['started'] == item2['started']:
    1211                                  return 0
    1212                          if item1['started'] > item2['started']:
    1213                                  return -1
    1214                          return 1
    1215
    1216                  # episodes: open, then reverse chronologically
    1217                  if isinstance(item1, gmEMRStructItems.cEpisode):
    1218                          # open episodes first
    1219                          if item1['episode_open']:
    1220                                  return -1
    1221                          if item2['episode_open']:
    1222                                  return 1
    1223                          start1 = item1.best_guess_clinical_start_date
    1224                          start2 = item2.best_guess_clinical_start_date
    1225                          if start1 == start2:
    1226                                  return 0
    1227                          if start1 < start2:
    1228                                  return 1
    1229                          return -1
    1230
    1231                  # issues: alpha by grouping, no grouping at the bottom
    1232                  if isinstance(item1, gmEMRStructItems.cHealthIssue):
    1233
    1234                          # no grouping below grouping
    1235                          if item1['grouping'] is None:
    1236                                  if item2['grouping'] is not None:
    1237                                          return 1
    1238
    1239                          # grouping above no grouping
    1240                          if item1['grouping'] is not None:
    1241                                  if item2['grouping'] is None:
    1242                                          return -1
    1243
    1244                          # both no grouping: alpha on description
    1245                          if (item1['grouping'] is None) and (item2['grouping'] is None):
    1246                                  if item1['description'].lower() < item2['description'].lower():
    1247                                          return -1
    1248                                  if item1['description'].lower() > item2['description'].lower():
    1249                                          return 1
    1250                                  return 0
    1251
    1252                          # both with grouping: alpha on grouping, then alpha on description
    1253                          if item1['grouping'] < item2['grouping']:
    1254                                  return -1
    1255
    1256                          if item1['grouping'] > item2['grouping']:
    1257                                  return 1
    1258
    1259                          if item1['description'].lower() < item2['description'].lower():
    1260                                  return -1
    1261
    1262                          if item1['description'].lower() > item2['description'].lower():
    1263                                  return 1
    1264
    1265                          return 0
    1266
    1267                  _log.error('unknown item type during sorting EMR tree:')
    1268                  _log.error('item1: %s', type(item1))
    1269                  _log.error('item2: %s', type(item2))
    1270
    1271                  return 0
    1272
    1273          #--------------------------------------------------------
    1274          # properties
    1275          #--------------------------------------------------------
    1276 -        def _get_patient(self):
    1277                  return self.__pat
    1278
    1279 -        def _set_patient(self, patient):
    1280                  if self.__pat == patient:
    1281                          return
    1282                  self.__pat = patient
    1283                  if patient is None:
    1284                          self.clear_tree()
    1285                          return
    1286                  return self.__populate_tree()
    1287
    1288          patient = property(_get_patient, _set_patient)
    1289          #--------------------------------------------------------
    1290 -        def _get_details_display_mode(self):
    1291                  return self.__soap_display_mode
    1292
    1293 -        def _set_details_display_mode(self, mode):
    1294                  if mode not in ['details', 'journal', 'revisions']:
    1295                          raise ValueError('details display mode must be one of "details", "journal", "revisions"')
    1296                  if self.__soap_display_mode == mode:
    1297                          return
    1298                  self.__soap_display_mode = mode
    1299                  self.__update_text_for_selected_node()
    1300
    1301          details_display_mode = property(_get_details_display_mode, _set_details_display_mode)
    1302
    1303  #================================================================
    1304  # FIXME: still needed ?
    1305  from Gnumed.wxGladeWidgets import wxgScrolledEMRTreePnl
    1306
    1307 -class cScrolledEMRTreePnl(wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl):
    1308          """A scrollable panel holding an EMR tree.
    1309
    1310          Lacks a widget to display details for selected items. The
    1311          tree data will be refetched - if necessary - whenever
    1312          repopulate_ui() is called, e.g., when the patient is changed.
    1313          """
    1314 -        def __init__(self, *args, **kwds):
    1315                  wxgScrolledEMRTreePnl.wxgScrolledEMRTreePnl.__init__(self, *args, **kwds)
    1316
    1317  #============================================================
    1318  from Gnumed.wxGladeWidgets import wxgSplittedEMRTreeBrowserPnl
    1319
    1320 -class cSplittedEMRTreeBrowserPnl(wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl):
    1321          """A splitter window holding an EMR tree.
    1322
    1323          The left hand side displays a scrollable EMR tree while
    1324          on the right details for selected items are displayed.
    1325
    1326          Expects to be put into a Notebook.
    1327          """
    1328 -        def __init__(self, *args, **kwds):
    1329                  wxgSplittedEMRTreeBrowserPnl.wxgSplittedEMRTreeBrowserPnl.__init__(self, *args, **kwds)
    1330                  self._pnl_emr_tree._emr_tree.soap_display = self._TCTRL_item_details
    1331                  self._pnl_emr_tree._emr_tree.image_display = self._PNL_visual_soap
    1332                  self._pnl_emr_tree._emr_tree.set_enable_display_mode_selection_callback(self.enable_display_mode_selection)
    1333                  self._pnl_emr_tree._emr_tree.soap_editor_adder = self._add_soap_editor
    1334                  self._pnl_emr_tree._emr_tree.edit_mode_selector = self._select_edit_mode
    1335                  self.__register_events()
    1336
    1337                  self.editing = False
    1338          #--------------------------------------------------------
    1339 -        def __register_events(self):
    1340                  gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
    1341                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
    1342                  return True
    1343
    1344          #--------------------------------------------------------
    1345 -        def _get_editing(self):
    1346                  return self.__editing
    1347
    1348 -        def _set_editing(self, editing):
    1349                  self.__editing = editing
    1350                  self.enable_display_mode_selection(enable = not self.__editing)
    1351                  if self.__editing:
    1352                          self._BTN_switch_browse_edit.SetLabel(_('&Browse %s') % gmTools.u_ellipsis)
    1353                          self._PNL_browse.Hide()
    1354                          self._PNL_visual_soap.Hide()
    1355                          self._PNL_edit.Show()
    1356                  else:
    1357                          self._BTN_switch_browse_edit.SetLabel(_('&New notes %s') % gmTools.u_ellipsis)
    1358                          self._PNL_edit.Hide()
    1359                          self._PNL_visual_soap.Show()
    1360                          self._PNL_browse.Show()
    1361                  self._PNL_right_side.GetSizer().Layout()
    1362
    1363          editing = property(_get_editing, _set_editing)
    1364
    1365          #--------------------------------------------------------
    1366          # event handler
    1367          #--------------------------------------------------------
    1368 -        def _on_pre_patient_unselection(self):
    1369                  self._pnl_emr_tree._emr_tree.patient = None
    1370                  self._PNL_edit.patient = None
    1371                  return True
    1372
    1373          #--------------------------------------------------------
    1374 -        def _on_post_patient_selection(self):
    1375                  if self.GetParent().GetCurrentPage() != self:
    1376                          return True
    1377                  self.repopulate_ui()
    1378                  return True
    1379
    1380          #--------------------------------------------------------
    1381 -        def _on_show_details_selected(self, event):
    1382                  self._pnl_emr_tree._emr_tree.details_display_mode = 'details'
    1383
    1384          #--------------------------------------------------------
    1385 -        def _on_show_journal_selected(self, event):
    1386                  self._pnl_emr_tree._emr_tree.details_display_mode = 'journal'
    1387
    1388          #--------------------------------------------------------
    1389 -        def _on_show_revisions_selected(self, event):
    1390                  self._pnl_emr_tree._emr_tree.details_display_mode = 'revisions'
    1391
    1392          #--------------------------------------------------------
    1393 -        def _on_switch_browse_edit_button_pressed(self, event):
    1394                  self.editing = not self.__editing
    1395
    1396          #--------------------------------------------------------
    1397          # external API
    1398          #--------------------------------------------------------
    1399 -        def repopulate_ui(self):
    1400                  """Fills UI with data."""
    1401                  pat = gmPerson.gmCurrentPatient()
    1402                  self._pnl_emr_tree._emr_tree.patient = pat
    1403                  self._PNL_edit.patient = pat
    1404                  self._splitter_browser.SetSashPosition(self._splitter_browser.GetSize()[0] // 3, True)
    1405
    1406                  return True
    1407
    1408          #--------------------------------------------------------
    1409 -        def enable_display_mode_selection(self, enable):
    1410                  if self.editing:
    1411                          enable = False
    1412                  if enable:
    1413                          self._RBTN_details.Enable(True)
    1414                          self._RBTN_journal.Enable(True)
    1415                          self._RBTN_revisions.Enable(True)
    1416                          return
    1417                  self._RBTN_details.Enable(False)
    1418                  self._RBTN_journal.Enable(False)
    1419                  self._RBTN_revisions.Enable(False)
    1420
    1421          #--------------------------------------------------------
    1422 -        def _add_soap_editor(self, problem=None, allow_same_problem=False):
    1423                  self._PNL_edit._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_same_problem)
    1424
    1425          #--------------------------------------------------------
    1426 -        def _select_edit_mode(self, edit=True):
    1427                  self.editing = edit
    1428
    1429  #================================================================
    1430  from Gnumed.wxGladeWidgets import wxgEMRJournalPluginPnl
    1431
    1432 -class cEMRJournalPluginPnl(wxgEMRJournalPluginPnl.wxgEMRJournalPluginPnl):
    1433
    1434 -        def __init__(self, *args, **kwds):
    1435
    1436                  wxgEMRJournalPluginPnl.wxgEMRJournalPluginPnl.__init__(self, *args, **kwds)
    1437                  self._TCTRL_journal.disable_keyword_expansions()
    1438                  self._TCTRL_journal.SetValue('')
    1439          #--------------------------------------------------------
    1440          # external API
    1441          #--------------------------------------------------------
    1442 -        def repopulate_ui(self):
    1443                  self._TCTRL_journal.SetValue('')
    1444                  exporter = gmPatientExporter.cEMRJournalExporter()
    1445                  if self._RBTN_by_encounter.GetValue():
    1446                          fname = exporter.save_to_file_by_encounter(patient = gmPerson.gmCurrentPatient())
    1447                  else:
    1448                          fname = exporter.save_to_file_by_mod_time(patient = gmPerson.gmCurrentPatient())
    1449
    1450                  f = io.open(fname, mode = 'rt', encoding = 'utf8', errors = 'replace')
    1451                  for line in f:
    1452                          self._TCTRL_journal.AppendText(line)
    1453                  f.close()
    1454
    1455                  self._TCTRL_journal.ShowPosition(self._TCTRL_journal.GetLastPosition())
    1456                  return True
    1457          #--------------------------------------------------------
    1458          # internal helpers
    1459          #--------------------------------------------------------
    1460 -        def __register_events(self):
    1461                  gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
    1462                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
    1463                  return True
    1464
    1465          #--------------------------------------------------------
    1466          # event handlers
    1467          #--------------------------------------------------------
    1468 -        def _on_pre_patient_unselection(self):
    1469                  self._TCTRL_journal.SetValue('')
    1470                  return True
    1471
    1472          #--------------------------------------------------------
    1473 -        def _on_post_patient_selection(self):
    1474                  if self.GetParent().GetCurrentPage() != self:
    1475                          return True
    1476                  self.repopulate_ui()
    1477                  return True
    1478
    1479          #--------------------------------------------------------
    1480 -        def _on_order_by_encounter_selected(self, event):
    1481                  self.repopulate_ui()
    1482
    1483          #--------------------------------------------------------
    1484 -        def _on_order_by_last_mod_selected(self, event):
    1485                  self.repopulate_ui()
    1486
    1487          #--------------------------------------------------------
    1488 -        def _on_button_find_pressed(self, event):
    1489                  self._TCTRL_journal.show_find_dialog(title = _('Find text in EMR Journal'))
    1490
    1491  #================================================================
    1492  from Gnumed.wxGladeWidgets import wxgEMRListJournalPluginPnl
    1493
    1494 -class cEMRListJournalPluginPnl(wxgEMRListJournalPluginPnl.wxgEMRListJournalPluginPnl):
    1495
    1496 -        def __init__(self, *args, **kwds):
    1497
    1498                  wxgEMRListJournalPluginPnl.wxgEMRListJournalPluginPnl.__init__(self, *args, **kwds)
    1499
    1500                  self._LCTRL_journal.select_callback = self._on_row_selected
    1501                  self._TCTRL_details.SetValue('')
    1502
    1503                  self.__load_timer = gmTimer.cTimer(callback = self._on_load_details, delay = 1000, cookie = 'EMRListJournalPluginDBLoadTimer')
    1504
    1505                  self.__data = {}
    1506
    1507          #--------------------------------------------------------
    1508          # external API
    1509          #--------------------------------------------------------
    1510 -        def repopulate_ui(self):
    1511                  self._LCTRL_journal.remove_items_safely()
    1512                  self._TCTRL_details.SetValue('')
    1513
    1514                  if self._RBTN_by_encounter.Value:               # (... is True:)
    1515                          order_by = 'encounter_started, pk_episode, src_table, scr, modified_when'
    1516                                                  #, clin_when (should not make a relevant difference)
    1517                          date_col_header = _('Encounter')
    1518                          date_fields = ['encounter_started', 'modified_when']
    1519                  elif self._RBTN_by_last_modified.Value: # (... is True:)
    1520                          order_by = 'modified_when, pk_episode, src_table, scr'
    1521                                                  #, clin_when (should not make a relevant difference)
    1522                          date_col_header = _('Modified')
    1523                          date_fields = ['modified_when']
    1524                  elif self._RBTN_by_item_time.Value:             # (... is True:)
    1525                          order_by = 'clin_when, pk_episode, src_table, scr, modified_when'
    1526                          date_col_header = _('Clinical time')
    1527                          date_fields = ['clin_when', 'modified_when']
    1528                  else:
    1529                          raise ValueError('invalid EMR journal list sort state')
    1530
    1531                  self._LCTRL_journal.set_columns([date_col_header, '', _('Entry'), _('Who / When')])
    1532                  self._LCTRL_journal.set_resize_column(3)
    1533
    1534                  journal = gmPerson.gmCurrentPatient().emr.get_as_journal(order_by = order_by)
    1535
    1536                  items = []
    1537                  data = []
    1538                  self.__data = {}
    1539                  prev_date = None
    1540                  for entry in journal:
    1541                          if entry['narrative'].strip() == '':
    1542                                  continue
    1543                          soap_cat = gmSoapDefs.soap_cat2l10n[entry['soap_cat']]
    1544                          who = '%s (%s)' % (entry['modified_by'], entry['date_modified'])
    1545                          try:
    1546                                  entry_date = gmDateTime.pydt_strftime(entry[date_fields[0]], '%Y-%m-%d')
    1547                          except KeyError:
    1548                                  entry_date = gmDateTime.pydt_strftime(entry[date_fields[1]], '%Y-%m-%d')
    1549                          if entry_date == prev_date:
    1550                                  date2show = ''
    1551                          else:
    1552                                  date2show = entry_date
    1553                                  prev_date = entry_date
    1554                          lines = entry['narrative'].strip().split('\n')
    1555                          line_0 = lines[0].rstrip()                              # assumes there's at least one line ...
    1556                          if len(lines) == 1:
    1557                                  delim = gmTools.u_box_horiz_light_3dashes * 10 + gmTools.u_box_T_left
    1558                          else:
    1559                                  delim = (gmTools.u_box_horiz_light_3dashes * 10) + gmTools.u_box_top_right_arc
    1560                          entry_line = '%s %s' % (line_0, delim)
    1561                          items.append([date2show, soap_cat, entry_line, who])
    1562                          try:
    1563                                  self.__data[entry['src_table']]
    1564                          except KeyError:
    1565                                  self.__data[entry['src_table']] = {}
    1566                          try:
    1567                                  self.__data[entry['src_table']][entry['src_pk']]
    1568                          except KeyError:
    1569                                  self.__data[entry['src_table']][entry['src_pk']] = {}
    1570                                  self.__data[entry['src_table']][entry['src_pk']]['entry'] = entry
    1571                                  self.__data[entry['src_table']][entry['src_pk']]['formatted_instance'] = None
    1572                                  if entry['encounter_started'] is None:
    1573                                          enc_duration = gmTools.u_diameter
    1574                                  else:
    1575                                          enc_duration = '%s - %s' % (
    1576                                                  gmDateTime.pydt_strftime(entry['encounter_started'], '%Y %b %d  %H:%M'),
    1577                                                  gmDateTime.pydt_strftime(entry['encounter_last_affirmed'], '%H:%M')
    1578                                          )
    1579                                  self.__data[entry['src_table']][entry['src_pk']]['formatted_header'] = _(
    1580                                          'Chart entry: %s       [#%s in %s]\n'
    1581                                          ' Modified: %s by %s (%s rev %s)\n'
    1582                                          '\n'
    1583                                          'Health issue: %s%s\n'
    1584                                          'Episode: %s%s\n'
    1585                                          'Encounter: %s%s'
    1586                                  ) % (
    1587                                          gmClinicalRecord.format_clin_root_item_type(entry['src_table']),
    1588                                          entry['src_pk'],
    1589                                          entry['src_table'],
    1590                                          entry['date_modified'],
    1591                                          entry['modified_by'],
    1592                                          gmTools.u_arrow2right,
    1593                                          entry['row_version'],
    1594                                          gmTools.coalesce(entry['health_issue'], gmTools.u_diameter, '%s'),
    1595                                          gmTools.bool2subst(entry['issue_active'], ' (' + _('active') + ')', ' (' + _('inactive') + ')', ''),
    1596                                          gmTools.coalesce(entry['episode'], gmTools.u_diameter, '%s'),
    1597                                          gmTools.bool2subst(entry['episode_open'], ' (' +  _('open') + ')', ' (' +  _('closed') + ')', ''),
    1598                                          enc_duration,
    1599                                          gmTools.coalesce(entry['encounter_l10n_type'], '', ' (%s)'),
    1600                                  )
    1601                                  self.__data[entry['src_table']][entry['src_pk']]['formatted_root_item'] = _(
    1602                                          '%s\n'
    1603                                          '\n'
    1604                                          '                        rev %s (%s) by %s in <%s>'
    1605                                  ) % (
    1606                                          entry['narrative'].strip(),
    1607                                          entry['row_version'],
    1608                                          entry['date_modified'],
    1609                                          entry['modified_by'],
    1610                                          entry['src_table']
    1611                                  )
    1612                          data.append ({
    1613                                  'table': entry['src_table'],
    1614                                  'pk': entry['src_pk']
    1615                          })
    1616                          if len(lines) > 1:
    1617                                  lines = lines[1:]
    1618                                  idx = 0
    1619                                  last_line = len(lines)
    1620                                  for entry_line in lines:
    1621                                          idx += 1
    1622                                          if entry_line.strip() == '':
    1623                                                  continue
    1624                                          if idx == last_line:
    1625                                                  bar = gmTools.u_box_bottom_left_arc
    1626                                          else:
    1627                                                  bar = gmTools.u_box_vert_light_4dashes
    1628                                          items.append(['', bar, entry_line.rstrip(), who])
    1629                                          data.append ({
    1630                                                  'table': entry['src_table'],
    1631                                                  'pk': entry['src_pk']
    1632                                          })
    1633
    1634                  self._LCTRL_journal.set_string_items(items)
    1635                  self._LCTRL_journal.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE_USEHEADER])
    1636                  self._LCTRL_journal.set_data(data)
    1637
    1638                  self._LCTRL_journal.SetFocus()
    1639                  return True
    1640
    1641          #--------------------------------------------------------
    1642          # internal helpers
    1643          #--------------------------------------------------------
    1644 -        def __register_events(self):
    1645                  gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
    1646                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
    1647                  return True
    1648
    1649          #--------------------------------------------------------
    1650          # event handlers
    1651          #--------------------------------------------------------
    1652 -        def _on_pre_patient_unselection(self):
    1653                  self._LCTRL_journal.remove_items_safely()
    1654                  self._TCTRL_details.SetValue('')
    1655                  self.__data = {}
    1656                  return True
    1657
    1658          #--------------------------------------------------------
    1659 -        def _on_post_patient_selection(self):
    1660                  if self.GetParent().GetCurrentPage() != self:
    1661                          return True
    1662                  self.repopulate_ui()
    1663                  return True
    1664
    1665          #--------------------------------------------------------
    1666 -        def _on_row_selected(self, evt):
    1667                  # FIXME: work on all selected
    1668                  data = self._LCTRL_journal.get_item_data(item_idx = evt.Index)
    1669                  if self.__data[data['table']][data['pk']]['formatted_instance'] is None:
    1670                          txt = _(
    1671                                  '%s\n'
    1672                                  '%s\n'
    1673                                  '%s'
    1674                          ) % (
    1675                                  self.__data[data['table']][data['pk']]['formatted_header'],
    1676                                  gmTools.u_box_horiz_4dashes * 40,
    1677                                  self.__data[data['table']][data['pk']]['formatted_root_item']
    1678                          )
    1679
    1680                          self._TCTRL_details.SetValue(txt)
    1681                          self.__load_timer.Stop()
    1682                          self.__load_timer.Start(oneShot = True)
    1683                          return
    1684
    1685                  txt = _(
    1686                          '%s\n'
    1687                          '%s\n'
    1688                          '%s'
    1689                  ) % (
    1690                          self.__data[data['table']][data['pk']]['formatted_header'],
    1691                          gmTools.u_box_horiz_4dashes * 40,
    1692                          self.__data[data['table']][data['pk']]['formatted_instance']
    1693                  )
    1694                  self._TCTRL_details.SetValue(txt)
    1695
    1696          #--------------------------------------------------------
    1697 -        def _on_load_details(self, cookie):
    1698                  data = self._LCTRL_journal.get_selected_item_data(only_one = True)
    1699                  if self.__data[data['table']][data['pk']]['formatted_instance'] is None:
    1700                          self.__data[data['table']][data['pk']]['formatted_instance'] = gmClinicalRecord.format_clin_root_item(data['table'], data['pk'], patient = gmPerson.gmCurrentPatient())
    1701                  txt = _(
    1702                          '%s\n'
    1703                          '%s\n'
    1704                          '%s'
    1705                  ) % (
    1706                          self.__data[data['table']][data['pk']]['formatted_header'],
    1707                          gmTools.u_box_horiz_4dashes * 40,
    1708                          self.__data[data['table']][data['pk']]['formatted_instance']
    1709                  )
    1710                  wx.CallAfter(self._TCTRL_details.SetValue, txt)
    1711
    1712          #--------------------------------------------------------
    1713 -        def _on_order_by_encounter_selected(self, event):
    1714                  self.repopulate_ui()
    1715
    1716          #--------------------------------------------------------
    1717 -        def _on_order_by_last_mod_selected(self, event):
    1718                  self.repopulate_ui()
    1719
    1720          #--------------------------------------------------------
    1721 -        def _on_order_by_item_time_selected(self, event):
    1722                  self.repopulate_ui()
    1723
    1724          #--------------------------------------------------------
    1725 -        def _on_order_by_item_time_selected(self, event):
    1726                  event.Skip()
    1727                  self.repopulate_ui()
    1728
    1729          #--------------------------------------------------------
    1730 -        def _on_edit_button_pressed(self, event):
    1731                  event.Skip()
    1732
    1733          #--------------------------------------------------------
    1734 -        def _on_delete_button_pressed(self, event):
    1735                  event.Skip()
    1736
    1737          #--------------------------------------------------------
    1738  #       def _on_button_find_pressed(self, event):
    1739  #               self._TCTRL_details.show_find_dialog(title = _('Find text in EMR Journal'))
    1740
    1741  #================================================================
    1742  # MAIN
    1743  #----------------------------------------------------------------
    1744  if __name__ == '__main__':
    1745
    1746          _log.info("starting emr browser...")
    1747
    1748          try:
    1749                  # obtain patient
    1750                  patient = gmPersonSearch.ask_for_patient()
    1751                  if patient is None:
    1752                          print("No patient. Exiting gracefully...")
    1753                          sys.exit(0)
    1754                  gmPatSearchWidgets.set_active_patient(patient = patient)
    1755
    1756                  # display standalone browser
    1757                  application = wx.PyWidgetTester(size=(800,600))
    1758                  emr_browser = cEMRBrowserPanel(application.frame, -1)
    1759                  emr_browser.refresh_tree()
    1760
    1761                  application.frame.Show(True)
    1762                  application.MainLoop()
    1763
    1764                  # clean up
    1765                  if patient is not None:
    1766                          try:
    1767                                  patient.cleanup()
    1768                          except:
    1769                                  print("error cleaning up patient")
    1770          except Exception:
    1771                  _log.exception("unhandled exception caught !")
    1772                  # but re-raise them
    1773                  raise
    1774
    1775          _log.info("closing emr browser...")
    1776
    1777  #================================================================
    1778