Gnumed.wxpython.gmEMRBrowser

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

# Source Code for Module Gnumed.wxpython.gmEMRBrowser

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