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