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

# Source Code for Module Gnumed.wxpython.gmEMRBrowser

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