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

# Source Code for Module Gnumed.wxpython.gmNarrativeWidgets

       1  """GNUmed narrative handling widgets."""
       2  #================================================================
       3  __version__ = "$Revision: 1.46 $"
       4  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
       5
       6  import sys, logging, os, os.path, time, re as regex, shutil
       7
       8
       9  import wx
      10  import wx.lib.expando as wx_expando
      11  import wx.lib.agw.supertooltip as agw_stt
      12  import wx.lib.statbmp as wx_genstatbmp
      13
      14
      15  if __name__ == '__main__':
      16          sys.path.insert(0, '../../')
      17  from Gnumed.pycommon import gmI18N
      18  from Gnumed.pycommon import gmDispatcher
      19  from Gnumed.pycommon import gmTools
      20  from Gnumed.pycommon import gmDateTime
      21  from Gnumed.pycommon import gmShellAPI
      22  from Gnumed.pycommon import gmPG2
      23  from Gnumed.pycommon import gmCfg
      24  from Gnumed.pycommon import gmMatchProvider
      25
      26  from Gnumed.business import gmPerson
      27  from Gnumed.business import gmEMRStructItems
      28  from Gnumed.business import gmClinNarrative
      29  from Gnumed.business import gmSurgery
      30  from Gnumed.business import gmForms
      31  from Gnumed.business import gmDocuments
      32  from Gnumed.business import gmPersonSearch
      33
      34  from Gnumed.wxpython import gmListWidgets
      35  from Gnumed.wxpython import gmEMRStructWidgets
      36  from Gnumed.wxpython import gmRegetMixin
      37  from Gnumed.wxpython import gmPhraseWheel
      38  from Gnumed.wxpython import gmGuiHelpers
      39  from Gnumed.wxpython import gmPatSearchWidgets
      40  from Gnumed.wxpython import gmCfgWidgets
      41  from Gnumed.wxpython import gmDocumentWidgets
      42
      43  from Gnumed.exporters import gmPatientExporter
      44
      45
      46  _log = logging.getLogger('gm.ui')
      47  _log.info(__version__)
      48  #============================================================
      49  # narrative related widgets/functions
      50  #------------------------------------------------------------
      51 -def move_progress_notes_to_another_encounter(parent=None, encounters=None, episodes=None, patient=None, move_all=False):
      52
      53          # sanity checks
      54          if patient is None:
      55                  patient = gmPerson.gmCurrentPatient()
      56
      57          if not patient.connected:
      58                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot move progress notes. No active patient.'))
      59                  return False
      60
      61          if parent is None:
      62                  parent = wx.GetApp().GetTopWindow()
      63
      64          emr = patient.get_emr()
      65
      66          if encounters is None:
      67                  encs = emr.get_encounters(episodes = episodes)
      68                  encounters = gmEMRStructWidgets.select_encounters (
      69                          parent = parent,
      70                          patient = patient,
      71                          single_selection = False,
      72                          encounters = encs
      73                  )
      74                  # cancelled
      75                  if encounters is None:
      76                          return True
      77                  # none selected
      78                  if len(encounters) == 0:
      79                          return True
      80
      81          notes = emr.get_clin_narrative (
      82                  encounters = encounters,
      83                  episodes = episodes
      84          )
      85
      86          # which narrative
      87          if move_all:
      88                  selected_narr = notes
      89          else:
      90                  selected_narr = gmListWidgets.get_choices_from_list (
      91                          parent = parent,
      92                          caption = _('Moving progress notes between encounters ...'),
      93                          single_selection = False,
      94                          can_return_empty = True,
      95                          data = notes,
      96                          msg = _('\n Select the progress notes to move from the list !\n\n'),
      97                          columns = [_('when'), _('who'), _('type'), _('entry')],
      98                          choices = [
      99                                  [       narr['date'].strftime('%x %H:%M'),
     100                                          narr['provider'],
     101                                          gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
     102                                          narr['narrative'].replace('\n', '/').replace('\r', '/')
     103                                  ] for narr in notes
     104                          ]
     105                  )
     106
     107          if not selected_narr:
     108                  return True
     109
     110          # which encounter to move to
     111          enc2move2 = gmEMRStructWidgets.select_encounters (
     112                  parent = parent,
     113                  patient = patient,
     114                  single_selection = True
     115          )
     116
     117          if not enc2move2:
     118                  return True
     119
     120          for narr in selected_narr:
     121                  narr['pk_encounter'] = enc2move2['pk_encounter']
     122                  narr.save()
     123
     124          return True
     125  #------------------------------------------------------------
     126 -def manage_progress_notes(parent=None, encounters=None, episodes=None, patient=None):
     127
     128          # sanity checks
     129          if patient is None:
     130                  patient = gmPerson.gmCurrentPatient()
     131
     132          if not patient.connected:
     133                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit progress notes. No active patient.'))
     134                  return False
     135
     136          if parent is None:
     137                  parent = wx.GetApp().GetTopWindow()
     138
     139          emr = patient.get_emr()
     140          #--------------------------
     141          def delete(item):
     142                  if item is None:
     143                          return False
     144                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
     145                          parent,
     146                          -1,
     147                          caption = _('Deleting progress note'),
     148                          question = _(
     149                                  'Are you positively sure you want to delete this\n'
     150                                  'progress note from the medical record ?\n'
     151                                  '\n'
     152                                  'Note that even if you chose to delete the entry it will\n'
     153                                  'still be (invisibly) kept in the audit trail to protect\n'
     154                                  'you from litigation because physical deletion is known\n'
     155                                  'to be unlawful in some jurisdictions.\n'
     156                          ),
     157                          button_defs = (
     158                                  {'label': _('Delete'), 'tooltip': _('Yes, delete the progress note.'), 'default': False},
     159                                  {'label': _('Cancel'), 'tooltip': _('No, do NOT delete the progress note.'), 'default': True}
     160                          )
     161                  )
     162                  decision = dlg.ShowModal()
     163
     164                  if decision != wx.ID_YES:
     165                          return False
     166
     167                  gmClinNarrative.delete_clin_narrative(narrative = item['pk_narrative'])
     168                  return True
     169          #--------------------------
     170          def edit(item):
     171                  if item is None:
     172                          return False
     173
     174                  dlg = gmGuiHelpers.cMultilineTextEntryDlg (
     175                          parent,
     176                          -1,
     177                          title = _('Editing progress note'),
     178                          msg = _('This is the original progress note:'),
     179                          data = item.format(left_margin = u' ', fancy = True),
     180                          text = item['narrative']
     181                  )
     182                  decision = dlg.ShowModal()
     183
     184                  if decision != wx.ID_SAVE:
     185                          return False
     186
     187                  val = dlg.value
     188                  dlg.Destroy()
     189                  if val.strip() == u'':
     190                          return False
     191
     192                  item['narrative'] = val
     193                  item.save_payload()
     194
     195                  return True
     196          #--------------------------
     197          def refresh(lctrl):
     198                  notes = emr.get_clin_narrative (
     199                          encounters = encounters,
     200                          episodes = episodes,
     201                          providers = [ gmStaff.gmCurrentProvider()['short_alias'] ]
     202                  )
     203                  lctrl.set_string_items(items = [
     204                          [       narr['date'].strftime('%x %H:%M'),
     205                                  gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
     206                                  narr['narrative'].replace('\n', '/').replace('\r', '/')
     207                          ] for narr in notes
     208                  ])
     209                  lctrl.set_data(data = notes)
     210          #--------------------------
     211
     212          gmListWidgets.get_choices_from_list (
     213                  parent = parent,
     214                  caption = _('Managing progress notes'),
     215                  msg = _(
     216                          '\n'
     217                          ' This list shows the progress notes by %s.\n'
     218                          '\n'
     219                  ) % gmStaff.gmCurrentProvider()['short_alias'],
     220                  columns = [_('when'), _('type'), _('entry')],
     221                  single_selection = True,
     222                  can_return_empty = False,
     223                  edit_callback = edit,
     224                  delete_callback = delete,
     225                  refresh_callback = refresh,
     226                  ignore_OK_button = True
     227          )
     228  #------------------------------------------------------------
     229 -def search_narrative_across_emrs(parent=None):
     230
     231          if parent is None:
     232                  parent = wx.GetApp().GetTopWindow()
     233
     234          searcher = wx.TextEntryDialog (
     235                  parent = parent,
     236                  message = _('Enter (regex) term to search for across all EMRs:'),
     237                  caption = _('Text search across all EMRs'),
     238                  style = wx.OK | wx.CANCEL | wx.CENTRE
     239          )
     240          result = searcher.ShowModal()
     241
     242          if result != wx.ID_OK:
     243                  return
     244
     245          wx.BeginBusyCursor()
     246          term = searcher.GetValue()
     247          searcher.Destroy()
     248          results = gmClinNarrative.search_text_across_emrs(search_term = term)
     249          wx.EndBusyCursor()
     250
     251          if len(results) == 0:
     252                  gmGuiHelpers.gm_show_info (
     253                          _(
     254                          'Nothing found for search term:\n'
     255                          ' "%s"'
     256                          ) % term,
     257                          _('Search results')
     258                  )
     259                  return
     260
     261          items = [ [gmPerson.cIdentity(aPK_obj =
     262          r['pk_patient'])['description_gender'], r['narrative'],
     263          r['src_table']] for r in results ]
     264
     265          selected_patient = gmListWidgets.get_choices_from_list (
     266                  parent = parent,
     267                  caption = _('Search results for %s') % term,
     268                  choices = items,
     269                  columns = [_('Patient'), _('Match'), _('Match location')],
     270                  data = [ r['pk_patient'] for r in results ],
     271                  single_selection = True,
     272                  can_return_empty = False
     273          )
     274
     275          if selected_patient is None:
     276                  return
     277
     278          wx.CallAfter(gmPatSearchWidgets.set_active_patient, patient = gmPerson.cIdentity(aPK_obj = selected_patient))
     279  #------------------------------------------------------------
     280 -def search_narrative_in_emr(parent=None, patient=None):
     281
     282          # sanity checks
     283          if patient is None:
     284                  patient = gmPerson.gmCurrentPatient()
     285
     286          if not patient.connected:
     287                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot search EMR. No active patient.'))
     288                  return False
     289
     290          if parent is None:
     291                  parent = wx.GetApp().GetTopWindow()
     292
     293          searcher = wx.TextEntryDialog (
     294                  parent = parent,
     295                  message = _('Enter search term:'),
     296                  caption = _('Text search of entire EMR of active patient'),
     297                  style = wx.OK | wx.CANCEL | wx.CENTRE
     298          )
     299          result = searcher.ShowModal()
     300
     301          if result != wx.ID_OK:
     302                  searcher.Destroy()
     303                  return False
     304
     305          wx.BeginBusyCursor()
     306          val = searcher.GetValue()
     307          searcher.Destroy()
     308          emr = patient.get_emr()
     309          rows = emr.search_narrative_simple(val)
     310          wx.EndBusyCursor()
     311
     312          if len(rows) == 0:
     313                  gmGuiHelpers.gm_show_info (
     314                          _(
     315                          'Nothing found for search term:\n'
     316                          ' "%s"'
     317                          ) % val,
     318                          _('Search results')
     319                  )
     320                  return True
     321
     322          txt = u''
     323          for row in rows:
     324                  txt += u'%s: %s\n' % (
     325                          row['soap_cat'],
     326                          row['narrative']
     327                  )
     328
     329                  txt += u' %s: %s - %s %s\n' % (
     330                          _('Encounter'),
     331                          row['encounter_started'].strftime('%x %H:%M'),
     332                          row['encounter_ended'].strftime('%H:%M'),
     333                          row['encounter_type']
     334                  )
     335                  txt += u' %s: %s\n' % (
     336                          _('Episode'),
     337                          row['episode']
     338                  )
     339                  txt += u' %s: %s\n\n' % (
     340                          _('Health issue'),
     341                          row['health_issue']
     342                  )
     343
     344          msg = _(
     345                  'Search term was: "%s"\n'
     346                  '\n'
     347                  'Search results:\n\n'
     348                  '%s\n'
     349          ) % (val, txt)
     350
     351          dlg = wx.MessageDialog (
     352                  parent = parent,
     353                  message = msg,
     354                  caption = _('Search results for %s') % val,
     355                  style = wx.OK | wx.STAY_ON_TOP
     356          )
     357          dlg.ShowModal()
     358          dlg.Destroy()
     359
     360          return True
     361  #------------------------------------------------------------
     362 -def export_narrative_for_medistar_import(parent=None, soap_cats=u'soapu', encounter=None):
     363
     364          # sanity checks
     365          pat = gmPerson.gmCurrentPatient()
     366          if not pat.connected:
     367                  gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR for Medistar. No active patient.'))
     368                  return False
     369
     370          if encounter is None:
     371                  encounter = pat.get_emr().active_encounter
     372
     373          if parent is None:
     374                  parent = wx.GetApp().GetTopWindow()
     375
     376          # get file name
     377          aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
     378                  # FIXME: make configurable
     379          aDefDir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed','export')))
     380                  # FIXME: make configurable
     381          fname = '%s-%s-%s-%s-%s.txt' % (
     382                  'Medistar-MD',
     383                  time.strftime('%Y-%m-%d',time.localtime()),
     384                  pat['lastnames'].replace(' ', '-'),
     385                  pat['firstnames'].replace(' ', '_'),
     386                  pat.get_formatted_dob(format = '%Y-%m-%d')
     387          )
     388          dlg = wx.FileDialog (
     389                  parent = parent,
     390                  message = _("Save EMR extract for MEDISTAR import as..."),
     391                  defaultDir = aDefDir,
     392                  defaultFile = fname,
     393                  wildcard = aWildcard,
     394                  style = wx.SAVE
     395          )
     396          choice = dlg.ShowModal()
     397          fname = dlg.GetPath()
     398          dlg.Destroy()
     399          if choice != wx.ID_OK:
     400                  return False
     401
     402          wx.BeginBusyCursor()
     403          _log.debug('exporting encounter for medistar import to [%s]', fname)
     404          exporter = gmPatientExporter.cMedistarSOAPExporter()
     405          successful, fname = exporter.export_to_file (
     406                  filename = fname,
     407                  encounter = encounter,
     408                  soap_cats = u'soapu',
     409                  export_to_import_file = True
     410          )
     411          if not successful:
     412                  gmGuiHelpers.gm_show_error (
     413                          _('Error exporting progress notes for MEDISTAR import.'),
     414                          _('MEDISTAR progress notes export')
     415                  )
     416                  wx.EndBusyCursor()
     417                  return False
     418
     419          gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported progress notes into file [%s] for Medistar import.') % fname, beep=False)
     420
     421          wx.EndBusyCursor()
     422          return True
     423  #------------------------------------------------------------
     424 -def select_narrative_from_episodes_new(parent=None, soap_cats=None):
     425          """soap_cats needs to be a list"""
     426
     427          if parent is None:
     428                  parent = wx.GetApp().GetTopWindow()
     429
     430          pat = gmPerson.gmCurrentPatient()
     431          emr = pat.get_emr()
     432
     433          selected_soap = {}
     434          selected_narrative_pks = []
     435
     436          #-----------------------------------------------
     437          def pick_soap_from_episode(episode):
     438
     439                  narr_for_epi = emr.get_clin_narrative(episodes = [episode['pk_episode']], soap_cats = soap_cats)
     440
     441                  if len(narr_for_epi) == 0:
     442                          gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episode.'))
     443                          return True
     444
     445                  dlg = cNarrativeListSelectorDlg (
     446                          parent = parent,
     447                          id = -1,
     448                          narrative = narr_for_epi,
     449                          msg = _(
     450                                  '\n This is the narrative (type %s) for the chosen episodes.\n'
     451                                  '\n'
     452                                  ' Now, mark the entries you want to include in your report.\n'
     453                          ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soapu')) ])
     454                  )
     455  #               selection_idxs = []
     456  #               for idx in range(len(narr_for_epi)):
     457  #                       if narr_for_epi[idx]['pk_narrative'] in selected_narrative_pks:
     458  #                               selection_idxs.append(idx)
     459  #               if len(selection_idxs) != 0:
     460  #                       dlg.set_selections(selections = selection_idxs)
     461                  btn_pressed = dlg.ShowModal()
     462                  selected_narr = dlg.get_selected_item_data()
     463                  dlg.Destroy()
     464
     465                  if btn_pressed == wx.ID_CANCEL:
     466                          return True
     467
     468                  selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ]
     469                  for narr in selected_narr:
     470                          selected_soap[narr['pk_narrative']] = narr
     471
     472                  print "before returning from picking soap"
     473
     474                  return True
     475          #-----------------------------------------------
     476          selected_episode_pks = []
     477
     478          all_epis = [ epi for epi in emr.get_episodes() if epi.has_narrative ]
     479
     480          if len(all_epis) == 0:
     481                  gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.'))
     482                  return []
     483
     484          dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg (
     485                  parent = parent,
     486                  id = -1,
     487                  episodes = all_epis,
     488                  msg = _('\n Select the the episode you want to report on.\n')
     489          )
     490  #       selection_idxs = []
     491  #       for idx in range(len(all_epis)):
     492  #               if all_epis[idx]['pk_episode'] in selected_episode_pks:
     493  #                       selection_idxs.append(idx)
     494  #       if len(selection_idxs) != 0:
     495  #               dlg.set_selections(selections = selection_idxs)
     496          dlg.left_extra_button = (
     497                  _('Pick SOAP'),
     498                  _('Pick SOAP entries from topmost selected episode'),
     499                  pick_soap_from_episode
     500          )
     501          btn_pressed = dlg.ShowModal()
     502          dlg.Destroy()
     503
     504          if btn_pressed == wx.ID_CANCEL:
     505                  return None
     506
     507          return selected_soap.values()
     508  #------------------------------------------------------------
     509 -def select_narrative_from_episodes(parent=None, soap_cats=None):
     510          """soap_cats needs to be a list"""
     511
     512          pat = gmPerson.gmCurrentPatient()
     513          emr = pat.get_emr()
     514
     515          if parent is None:
     516                  parent = wx.GetApp().GetTopWindow()
     517
     518          selected_soap = {}
     519          selected_issue_pks = []
     520          selected_episode_pks = []
     521          selected_narrative_pks = []
     522
     523          while 1:
     524                  # 1) select health issues to select episodes from
     525                  all_issues = emr.get_health_issues()
     526                  all_issues.insert(0, gmEMRStructItems.get_dummy_health_issue())
     527                  dlg = gmEMRStructWidgets.cIssueListSelectorDlg (
     528                          parent = parent,
     529                          id = -1,
     530                          issues = all_issues,
     531                          msg = _('\n In the list below mark the health issues you want to report on.\n')
     532                  )
     533                  selection_idxs = []
     534                  for idx in range(len(all_issues)):
     535                          if all_issues[idx]['pk_health_issue'] in selected_issue_pks:
     536                                  selection_idxs.append(idx)
     537                  if len(selection_idxs) != 0:
     538                          dlg.set_selections(selections = selection_idxs)
     539                  btn_pressed = dlg.ShowModal()
     540                  selected_issues = dlg.get_selected_item_data()
     541                  dlg.Destroy()
     542
     543                  if btn_pressed == wx.ID_CANCEL:
     544                          return selected_soap.values()
     545
     546                  selected_issue_pks = [ i['pk_health_issue'] for i in selected_issues ]
     547
     548                  while 1:
     549                          # 2) select episodes to select items from
     550                          all_epis = emr.get_episodes(issues = selected_issue_pks)
     551
     552                          if len(all_epis) == 0:
     553                                  gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.'))
     554                                  break
     555
     556                          dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg (
     557                                  parent = parent,
     558                                  id = -1,
     559                                  episodes = all_epis,
     560                                  msg = _(
     561                                          '\n These are the episodes known for the health issues just selected.\n\n'
     562                                          ' Now, mark the the episodes you want to report on.\n'
     563                                  )
     564                          )
     565                          selection_idxs = []
     566                          for idx in range(len(all_epis)):
     567                                  if all_epis[idx]['pk_episode'] in selected_episode_pks:
     568                                          selection_idxs.append(idx)
     569                          if len(selection_idxs) != 0:
     570                                  dlg.set_selections(selections = selection_idxs)
     571                          btn_pressed = dlg.ShowModal()
     572                          selected_epis = dlg.get_selected_item_data()
     573                          dlg.Destroy()
     574
     575                          if btn_pressed == wx.ID_CANCEL:
     576                                  break
     577
     578                          selected_episode_pks = [ i['pk_episode'] for i in selected_epis ]
     579
     580                          # 3) select narrative corresponding to the above constraints
     581                          all_narr = emr.get_clin_narrative(episodes = selected_episode_pks, soap_cats = soap_cats)
     582
     583                          if len(all_narr) == 0:
     584                                  gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episodes.'))
     585                                  continue
     586
     587                          dlg = cNarrativeListSelectorDlg (
     588                                  parent = parent,
     589                                  id = -1,
     590                                  narrative = all_narr,
     591                                  msg = _(
     592                                          '\n This is the narrative (type %s) for the chosen episodes.\n\n'
     593                                          ' Now, mark the entries you want to include in your report.\n'
     594                                  ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soapu')) ])
     595                          )
     596                          selection_idxs = []
     597                          for idx in range(len(all_narr)):
     598                                  if all_narr[idx]['pk_narrative'] in selected_narrative_pks:
     599                                          selection_idxs.append(idx)
     600                          if len(selection_idxs) != 0:
     601                                  dlg.set_selections(selections = selection_idxs)
     602                          btn_pressed = dlg.ShowModal()
     603                          selected_narr = dlg.get_selected_item_data()
     604                          dlg.Destroy()
     605
     606                          if btn_pressed == wx.ID_CANCEL:
     607                                  continue
     608
     609                          selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ]
     610                          for narr in selected_narr:
     611                                  selected_soap[narr['pk_narrative']] = narr
     612  #------------------------------------------------------------
     613 -class cNarrativeListSelectorDlg(gmListWidgets.cGenericListSelectorDlg):
     614
     615 -        def __init__(self, *args, **kwargs):
     616
     617                  narrative = kwargs['narrative']
     618                  del kwargs['narrative']
     619
     620                  gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
     621
     622                  self.SetTitle(_('Select the narrative you are interested in ...'))
     623                  # FIXME: add epi/issue
     624                  self._LCTRL_items.set_columns([_('when'), _('who'), _('type'), _('entry')]) #, _('Episode'), u'', _('Health Issue')])
     625                  # FIXME: date used should be date of encounter, not date_modified
     626                  self._LCTRL_items.set_string_items (
     627                          items = [ [narr['date'].strftime('%x %H:%M'), narr['provider'], gmClinNarrative.soap_cat2l10n[narr['soap_cat']], narr['narrative'].replace('\n', '/').replace('\r', '/')] for narr in narrative ]
     628                  )
     629                  self._LCTRL_items.set_column_widths()
     630                  self._LCTRL_items.set_data(data = narrative)
     631  #------------------------------------------------------------
     632  from Gnumed.wxGladeWidgets import wxgMoveNarrativeDlg
     633
     634 -class cMoveNarrativeDlg(wxgMoveNarrativeDlg.wxgMoveNarrativeDlg):
     635
     636 -        def __init__(self, *args, **kwargs):
     637
     638                  self.encounter = kwargs['encounter']
     639                  self.source_episode = kwargs['episode']
     640                  del kwargs['encounter']
     641                  del kwargs['episode']
     642
     643                  wxgMoveNarrativeDlg.wxgMoveNarrativeDlg.__init__(self, *args, **kwargs)
     644
     645                  self.LBL_source_episode.SetLabel(u'%s%s' % (self.source_episode['description'], gmTools.coalesce(self.source_episode['health_issue'], u'', u' (%s)')))
     646                  self.LBL_encounter.SetLabel('%s: %s %s - %s' % (
     647                          self.encounter['started'].strftime('%x').decode(gmI18N.get_encoding()),
     648                          self.encounter['l10n_type'],
     649                          self.encounter['started'].strftime('%H:%M'),
     650                          self.encounter['last_affirmed'].strftime('%H:%M')
     651                  ))
     652                  pat = gmPerson.gmCurrentPatient()
     653                  emr = pat.get_emr()
     654                  narr = emr.get_clin_narrative(episodes=[self.source_episode['pk_episode']], encounters=[self.encounter['pk_encounter']])
     655                  if len(narr) == 0:
     656                          narr = [{'narrative': _('There is no narrative for this episode in this encounter.')}]
     657                  self.LBL_narrative.SetLabel(u'\n'.join([n['narrative'] for n in narr]))
     658
     659          #------------------------------------------------------------
     660 -        def _on_move_button_pressed(self, event):
     661
     662                  target_episode = self._PRW_episode_selector.GetData(can_create = False)
     663
     664                  if target_episode is None:
     665                          gmDispatcher.send(signal='statustext', msg=_('Must select episode to move narrative to first.'))
     666                          # FIXME: set to pink
     667                          self._PRW_episode_selector.SetFocus()
     668                          return False
     669
     670                  target_episode = gmEMRStructItems.cEpisode(aPK_obj=target_episode)
     671
     672                  self.encounter.transfer_clinical_data (
     673                          source_episode = self.source_episode,
     674                          target_episode = target_episode
     675                  )
     676
     677                  if self.IsModal():
     678                          self.EndModal(wx.ID_OK)
     679                  else:
     680                          self.Close()
     681  #============================================================
     682  from Gnumed.wxGladeWidgets import wxgSoapPluginPnl
     683
     684 -class cSoapPluginPnl(wxgSoapPluginPnl.wxgSoapPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
     685          """A panel for in-context editing of progress notes.
     686
     687          Expects to be used as a notebook page.
     688
     689          Left hand side:
     690          - problem list (health issues and active episodes)
     691          - previous notes
     692
     693          Right hand side:
     694          - encounter details fields
     695          - notebook with progress note editors
     696          - visual progress notes
     697          - hints
     698
     699          Listens to patient change signals, thus acts on the current patient.
     700          """
     701 -        def __init__(self, *args, **kwargs):
     702
     703                  wxgSoapPluginPnl.wxgSoapPluginPnl.__init__(self, *args, **kwargs)
     704                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
     705
     706                  self.__pat = gmPerson.gmCurrentPatient()
     707                  self.__patient_just_changed = False
     708                  self.__init_ui()
     709                  self.__reset_ui_content()
     710
     711                  self.__register_interests()
     712          #--------------------------------------------------------
     713          # public API
     714          #--------------------------------------------------------
     715 -        def save_encounter(self):
     716
     717                  if not self.__encounter_valid_for_save():
     718                          return False
     719
     720                  emr = self.__pat.get_emr()
     721                  enc = emr.active_encounter
     722
     723                  rfe = self._TCTRL_rfe.GetValue().strip()
     724                  if len(rfe) == 0:
     725                          enc['reason_for_encounter'] = None
     726                  else:
     727                          enc['reason_for_encounter'] = rfe
     728                  aoe = self._TCTRL_aoe.GetValue().strip()
     729                  if len(aoe) == 0:
     730                          enc['assessment_of_encounter'] = None
     731                  else:
     732                          enc['assessment_of_encounter'] = aoe
     733
     734                  enc.save_payload()
     735
     736                  enc.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
     737                  enc.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
     738
     739                  return True
     740          #--------------------------------------------------------
     741          # internal helpers
     742          #--------------------------------------------------------
     743 -        def __init_ui(self):
     744                  self._LCTRL_active_problems.set_columns([_('Last'), _('Problem'), _('In health issue')])
     745                  self._LCTRL_active_problems.set_string_items()
     746
     747                  self._splitter_main.SetSashGravity(0.5)
     748                  self._splitter_left.SetSashGravity(0.5)
     749
     750                  splitter_size = self._splitter_main.GetSizeTuple()[0]
     751                  self._splitter_main.SetSashPosition(splitter_size * 3 / 10, True)
     752
     753                  splitter_size = self._splitter_left.GetSizeTuple()[1]
     754                  self._splitter_left.SetSashPosition(splitter_size * 6 / 20, True)
     755
     756                  self._NB_soap_editors.DeleteAllPages()
     757                  self._NB_soap_editors.MoveAfterInTabOrder(self._PRW_aoe_codes)
     758          #--------------------------------------------------------
     759 -        def _on_encounter_start_lost_focus(self):
     760                  start = self._PRW_encounter_start.GetData()
     761                  if start is None:
     762                          return
     763                  start = start.get_pydt()
     764
     765                  end = self._PRW_encounter_end.GetData()
     766                  if end is None:
     767                          fts = gmDateTime.cFuzzyTimestamp (
     768                                  timestamp = start,
     769                                  accuracy = gmDateTime.acc_minutes
     770                          )
     771                          self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
     772                          return
     773                  end = end.get_pydt()
     774
     775                  if start > end:
     776                          end = end.replace (
     777                                  year = start.year,
     778                                  month = start.month,
     779                                  day = start.day
     780                          )
     781                          fts = gmDateTime.cFuzzyTimestamp (
     782                                  timestamp = end,
     783                                  accuracy = gmDateTime.acc_minutes
     784                          )
     785                          self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
     786                          return
     787
     788                  emr = self.__pat.get_emr()
     789                  if start != emr.active_encounter['started']:
     790                          end = end.replace (
     791                                  year = start.year,
     792                                  month = start.month,
     793                                  day = start.day
     794                          )
     795                          fts = gmDateTime.cFuzzyTimestamp (
     796                                  timestamp = end,
     797                                  accuracy = gmDateTime.acc_minutes
     798                          )
     799                          self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
     800                          return
     801
     802                  return
     803          #--------------------------------------------------------
     804 -        def __reset_ui_content(self):
     805                  """Clear all information from input panel."""
     806
     807                  self._LCTRL_active_problems.set_string_items()
     808
     809                  self._TCTRL_recent_notes.SetValue(u'')
     810                  self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on selected problem'))
     811
     812                  self._TCTRL_rfe.SetValue(u'')
     813                  self._PRW_rfe_codes.SetText(suppress_smarts = True)
     814                  self._TCTRL_aoe.SetValue(u'')
     815                  self._PRW_aoe_codes.SetText(suppress_smarts = True)
     816
     817                  self._NB_soap_editors.DeleteAllPages()
     818                  self._NB_soap_editors.add_editor()
     819          #--------------------------------------------------------
     820 -        def __refresh_problem_list(self):
     821                  """Update health problems list."""
     822
     823                  self._LCTRL_active_problems.set_string_items()
     824
     825                  emr = self.__pat.get_emr()
     826                  problems = emr.get_problems (
     827                          include_closed_episodes = self._CHBOX_show_closed_episodes.IsChecked(),
     828                          include_irrelevant_issues = self._CHBOX_irrelevant_issues.IsChecked()
     829                  )
     830
     831                  list_items = []
     832                  active_problems = []
     833                  for problem in problems:
     834                          if not problem['problem_active']:
     835                                  if not problem['is_potential_problem']:
     836                                          continue
     837
     838                          active_problems.append(problem)
     839
     840                          if problem['type'] == 'issue':
     841                                  issue = emr.problem2issue(problem)
     842                                  last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue'])
     843                                  if last_encounter is None:
     844                                          last = issue['modified_when'].strftime('%m/%Y')
     845                                  else:
     846                                          last = last_encounter['last_affirmed'].strftime('%m/%Y')
     847
     848                                  list_items.append([last, problem['problem'], gmTools.u_down_left_arrow])                #gmTools.u_left_arrow
     849
     850                          elif problem['type'] == 'episode':
     851                                  epi = emr.problem2episode(problem)
     852                                  last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode'])
     853                                  if last_encounter is None:
     854                                          last = epi['episode_modified_when'].strftime('%m/%Y')
     855                                  else:
     856                                          last = last_encounter['last_affirmed'].strftime('%m/%Y')
     857
     858                                  list_items.append ([
     859                                          last,
     860                                          problem['problem'],
     861                                          gmTools.coalesce(initial = epi['health_issue'], instead = u'?')         #gmTools.u_diameter
     862                                  ])
     863
     864                  self._LCTRL_active_problems.set_string_items(items = list_items)
     865                  self._LCTRL_active_problems.set_column_widths()
     866                  self._LCTRL_active_problems.set_data(data = active_problems)
     867
     868                  showing_potential_problems = (
     869                          self._CHBOX_show_closed_episodes.IsChecked()
     870                                  or
     871                          self._CHBOX_irrelevant_issues.IsChecked()
     872                  )
     873                  if showing_potential_problems:
     874                          self._SZR_problem_list_staticbox.SetLabel(_('%s (active+potential) problems') % len(list_items))
     875                  else:
     876                          self._SZR_problem_list_staticbox.SetLabel(_('%s active problems') % len(list_items))
     877
     878                  return True
     879          #--------------------------------------------------------
     880 -        def __get_soap_for_issue_problem(self, problem=None):
     881                  soap = u''
     882                  emr = self.__pat.get_emr()
     883                  prev_enc = emr.get_last_but_one_encounter(issue_id = problem['pk_health_issue'])
     884                  if prev_enc is not None:
     885                          soap += prev_enc.format (
     886                                  issues = [ problem['pk_health_issue'] ],
     887                                  with_soap = True,
     888                                  with_docs = False,
     889                                  with_tests = False,
     890                                  patient = self.__pat,
     891                                  fancy_header = False,
     892                                  with_rfe_aoe = True
     893                          )
     894
     895                  tmp = emr.active_encounter.format_soap (
     896                          soap_cats = 'soapu',
     897                          emr = emr,
     898                          issues = [ problem['pk_health_issue'] ],
     899                  )
     900                  if len(tmp) > 0:
     901                          soap += _('Current encounter:') + u'\n'
     902                          soap += u'\n'.join(tmp) + u'\n'
     903
     904                  if problem['summary'] is not None:
     905                          soap += u'\n-- %s ----------\n%s' % (
     906                                  _('Cumulative summary'),
     907                                  gmTools.wrap (
     908                                          text = problem['summary'],
     909                                          width = 45,
     910                                          initial_indent = u' ',
     911                                          subsequent_indent = u' '
     912                                  ).strip('\n')
     913                          )
     914
     915                  return soap
     916          #--------------------------------------------------------
     917 -        def __get_soap_for_episode_problem(self, problem=None):
     918                  soap = u''
     919                  emr = self.__pat.get_emr()
     920                  prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_episode'])
     921                  if prev_enc is not None:
     922                          soap += prev_enc.format (
     923                                  episodes = [ problem['pk_episode'] ],
     924                                  with_soap = True,
     925                                  with_docs = False,
     926                                  with_tests = False,
     927                                  patient = self.__pat,
     928                                  fancy_header = False,
     929                                  with_rfe_aoe = True
     930                          )
     931                  else:
     932                          if problem['pk_health_issue'] is not None:
     933                                  prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_health_issue'])
     934                                  if prev_enc is not None:
     935                                          soap += prev_enc.format (
     936                                                  with_soap = True,
     937                                                  with_docs = False,
     938                                                  with_tests = False,
     939                                                  patient = self.__pat,
     940                                                  issues = [ problem['pk_health_issue'] ],
     941                                                  fancy_header = False,
     942                                                  with_rfe_aoe = True
     943                                          )
     944
     945                  tmp = emr.active_encounter.format_soap (
     946                          soap_cats = 'soapu',
     947                          emr = emr,
     948                          issues = [ problem['pk_health_issue'] ],
     949                  )
     950                  if len(tmp) > 0:
     951                          soap += _('Current encounter:') + u'\n'
     952                          soap += u'\n'.join(tmp) + u'\n'
     953
     954                  if problem['summary'] is not None:
     955                          soap += u'\n-- %s ----------\n%s' % (
     956                                  _('Cumulative summary'),
     957                                  gmTools.wrap (
     958                                          text = problem['summary'],
     959                                          width = 45,
     960                                          initial_indent = u' ',
     961                                          subsequent_indent = u' '
     962                                  ).strip('\n')
     963                          )
     964
     965                  return soap
     966          #--------------------------------------------------------
     967 -        def __refresh_current_editor(self):
     968                  self._NB_soap_editors.refresh_current_editor()
     969          #--------------------------------------------------------
     970 -        def __setup_initial_patient_editors(self):
     971                  if not self.__patient_just_changed:
     972                          return
     973
     974                  dbcfg = gmCfg.cCfgSQL()
     975                  auto_open_recent_problems = bool(dbcfg.get2 (
     976                          option = u'horstspace.soap_editor.auto_open_latest_episodes',
     977                          workplace = gmSurgery.gmCurrentPractice().active_workplace,
     978                          bias = u'user',
     979                          default = True
     980                  ))
     981
     982                  self.__patient_just_changed = False
     983                  emr = self.__pat.get_emr()
     984                  recent_epis = emr.active_encounter.get_episodes()
     985                  prev_enc = emr.get_last_but_one_encounter()
     986                  if prev_enc is not None:
     987                          recent_epis.extend(prev_enc.get_episodes())
     988
     989                  for epi in recent_epis:
     990                          if not epi['episode_open']:
     991                                  continue
     992                          self._NB_soap_editors.add_editor(problem = epi, allow_same_problem = False)
     993          #--------------------------------------------------------
     994 -        def __refresh_recent_notes(self, problem=None):
     995                  """This refreshes the recent-notes part."""
     996
     997                  soap = u''
     998                  caption = u'<?>'
     999
    1000                  if problem['type'] == u'issue':
    1001                          caption = problem['problem'][:35]
    1002                          soap = self.__get_soap_for_issue_problem(problem = problem)
    1003
    1004                  elif problem['type'] == u'episode':
    1005                          caption = problem['problem'][:35]
    1006                          soap = self.__get_soap_for_episode_problem(problem = problem)
    1007
    1008                  self._TCTRL_recent_notes.SetValue(soap)
    1009                  self._TCTRL_recent_notes.ShowPosition(self._TCTRL_recent_notes.GetLastPosition())
    1010                  self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on %s%s%s') % (
    1011                          gmTools.u_left_double_angle_quote,
    1012                          caption,
    1013                          gmTools.u_right_double_angle_quote
    1014                  ))
    1015
    1016                  self._TCTRL_recent_notes.Refresh()
    1017
    1018                  return True
    1019          #--------------------------------------------------------
    1020 -        def __refresh_encounter(self):
    1021                  """Update encounter fields."""
    1022
    1023                  emr = self.__pat.get_emr()
    1024                  enc = emr.active_encounter
    1025
    1026                  self._TCTRL_rfe.SetValue(gmTools.coalesce(enc['reason_for_encounter'], u''))
    1027                  val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(enc.generic_codes_rfe)
    1028                  self._PRW_rfe_codes.SetText(val, data)
    1029
    1030                  self._TCTRL_aoe.SetValue(gmTools.coalesce(enc['assessment_of_encounter'], u''))
    1031                  val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(enc.generic_codes_aoe)
    1032                  self._PRW_aoe_codes.SetText(val, data)
    1033
    1034                  self._TCTRL_rfe.Refresh()
    1035                  self._PRW_rfe_codes.Refresh()
    1036                  self._TCTRL_aoe.Refresh()
    1037                  self._PRW_aoe_codes.Refresh()
    1038          #--------------------------------------------------------
    1039 -        def __encounter_modified(self):
    1040                  """Assumes that the field data is valid."""
    1041
    1042                  emr = self.__pat.get_emr()
    1043                  enc = emr.active_encounter
    1044
    1045                  data = {
    1046                          'pk_type': enc['pk_type'],
    1047                          'reason_for_encounter': gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u''),
    1048                          'assessment_of_encounter': gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
    1049                          'pk_location': enc['pk_location'],
    1050                          'pk_patient': enc['pk_patient'],
    1051                          'pk_generic_codes_rfe': self._PRW_rfe_codes.GetData(),
    1052                          'pk_generic_codes_aoe': self._PRW_aoe_codes.GetData(),
    1053                          'started': enc['started'],
    1054                          'last_affirmed': enc['last_affirmed']
    1055                  }
    1056
    1057                  return not enc.same_payload(another_object = data)
    1058          #--------------------------------------------------------
    1059 -        def __encounter_valid_for_save(self):
    1060                  return True
    1061          #--------------------------------------------------------
    1062          # event handling
    1063          #--------------------------------------------------------
    1064 -        def __register_interests(self):
    1065                  """Configure enabled event signals."""
    1066                  # client internal signals
    1067                  gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
    1068                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
    1069                  gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db)
    1070                  gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db)
    1071                  gmDispatcher.connect(signal = u'episode_code_mod_db', receiver = self._on_episode_issue_mod_db)
    1072                  gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db)                    # visual progress notes
    1073                  gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified)
    1074                  gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched)
    1075                  gmDispatcher.connect(signal = u'rfe_code_mod_db', receiver = self._on_encounter_code_modified)
    1076                  gmDispatcher.connect(signal = u'aoe_code_mod_db', receiver = self._on_encounter_code_modified)
    1077
    1078                  # synchronous signals
    1079                  self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback)
    1080                  gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
    1081          #--------------------------------------------------------
    1082 -        def _pre_selection_callback(self):
    1083                  """Another patient is about to be activated.
    1084
    1085                  Patient change will not proceed before this returns True.
    1086                  """
    1087                  # don't worry about the encounter here - it will be offered
    1088                  # for editing higher up if anything was saved to the EMR
    1089                  if not self.__pat.connected:
    1090                          return True
    1091                  return self._NB_soap_editors.warn_on_unsaved_soap()
    1092          #--------------------------------------------------------
    1093 -        def _pre_exit_callback(self):
    1094                  """The client is about to be shut down.
    1095
    1096                  Shutdown will not proceed before this returns.
    1097                  """
    1098                  if not self.__pat.connected:
    1099                          return True
    1100
    1101  #               if self.__encounter_modified():
    1102  #                       do_save_enc = gmGuiHelpers.gm_show_question (
    1103  #                               aMessage = _(
    1104  #                                       'You have modified the details\n'
    1105  #                                       'of the current encounter.\n'
    1106  #                                       '\n'
    1107  #                                       'Do you want to save those changes ?'
    1108  #                               ),
    1109  #                               aTitle = _('Starting new encounter')
    1110  #                       )
    1111  #                       if do_save_enc:
    1112  #                               if not self.save_encounter():
    1113  #                                       gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True)
    1114
    1115                  emr = self.__pat.get_emr()
    1116                  saved = self._NB_soap_editors.save_all_editors (
    1117                          emr = emr,
    1118                          episode_name_candidates = [
    1119                                  gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
    1120                                  gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
    1121                          ]
    1122                  )
    1123                  if not saved:
    1124                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
    1125                  return True
    1126          #--------------------------------------------------------
    1127 -        def _on_pre_patient_selection(self):
    1128                  wx.CallAfter(self.__on_pre_patient_selection)
    1129          #--------------------------------------------------------
    1130 -        def __on_pre_patient_selection(self):
    1131                  self.__reset_ui_content()
    1132          #--------------------------------------------------------
    1133 -        def _on_post_patient_selection(self):
    1134                  wx.CallAfter(self._schedule_data_reget)
    1135                  self.__patient_just_changed = True
    1136          #--------------------------------------------------------
    1137 -        def _on_doc_mod_db(self):
    1138                  wx.CallAfter(self.__refresh_current_editor)
    1139          #--------------------------------------------------------
    1140 -        def _on_episode_issue_mod_db(self):
    1141                  wx.CallAfter(self._schedule_data_reget)
    1142          #--------------------------------------------------------
    1143 -        def _on_encounter_code_modified(self):
    1144                  emr = self.__pat.get_emr()
    1145                  emr.active_encounter.refetch_payload()
    1146                  wx.CallAfter(self.__refresh_encounter)
    1147          #--------------------------------------------------------
    1148 -        def _on_current_encounter_modified(self):
    1149                  wx.CallAfter(self.__refresh_encounter)
    1150          #--------------------------------------------------------
    1151 -        def _on_current_encounter_switched(self):
    1152                  wx.CallAfter(self.__on_current_encounter_switched)
    1153          #--------------------------------------------------------
    1154 -        def __on_current_encounter_switched(self):
    1155                  self.__refresh_encounter()
    1156          #--------------------------------------------------------
    1157          # problem list specific events
    1158          #--------------------------------------------------------
    1159 -        def _on_problem_focused(self, event):
    1160                  """Show related note at the bottom."""
    1161                  pass
    1162          #--------------------------------------------------------
    1163 -        def _on_problem_rclick(self, event):
    1164                  problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
    1165                  if problem['type'] == u'issue':
    1166                          gmEMRStructWidgets.edit_health_issue(parent = self, issue = problem.get_as_health_issue())
    1167                          return
    1168
    1169                  if problem['type'] == u'episode':
    1170                          gmEMRStructWidgets.edit_episode(parent = self, episode = problem.get_as_episode())
    1171                          return
    1172
    1173                  event.Skip()
    1174          #--------------------------------------------------------
    1175 -        def _on_problem_selected(self, event):
    1176                  """Show related note at the bottom."""
    1177                  emr = self.__pat.get_emr()
    1178                  self.__refresh_recent_notes (
    1179                          problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
    1180                  )
    1181          #--------------------------------------------------------
    1182 -        def _on_problem_activated(self, event):
    1183                  """Open progress note editor for this problem.
    1184                  """
    1185                  problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
    1186                  if problem is None:
    1187                          return True
    1188
    1189                  dbcfg = gmCfg.cCfgSQL()
    1190                  allow_duplicate_editors = bool(dbcfg.get2 (
    1191                          option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
    1192                          workplace = gmSurgery.gmCurrentPractice().active_workplace,
    1193                          bias = u'user',
    1194                          default = False
    1195                  ))
    1196                  if self._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_duplicate_editors):
    1197                          return True
    1198
    1199                  gmGuiHelpers.gm_show_error (
    1200                          aMessage = _(
    1201                                  'Cannot open progress note editor for\n\n'
    1202                                  '[%s].\n\n'
    1203                          ) % problem['problem'],
    1204                          aTitle = _('opening progress note editor')
    1205                  )
    1206                  event.Skip()
    1207                  return False
    1208          #--------------------------------------------------------
    1209 -        def _on_show_closed_episodes_checked(self, event):
    1210                  self.__refresh_problem_list()
    1211          #--------------------------------------------------------
    1212 -        def _on_irrelevant_issues_checked(self, event):
    1213                  self.__refresh_problem_list()
    1214          #--------------------------------------------------------
    1215          # SOAP editor specific buttons
    1216          #--------------------------------------------------------
    1217 -        def _on_discard_editor_button_pressed(self, event):
    1218                  self._NB_soap_editors.close_current_editor()
    1219                  event.Skip()
    1220          #--------------------------------------------------------
    1221 -        def _on_new_editor_button_pressed(self, event):
    1222                  self._NB_soap_editors.add_editor()
    1223                  event.Skip()
    1224          #--------------------------------------------------------
    1225 -        def _on_clear_editor_button_pressed(self, event):
    1226                  self._NB_soap_editors.clear_current_editor()
    1227                  event.Skip()
    1228          #--------------------------------------------------------
    1229 -        def _on_save_note_button_pressed(self, event):
    1230                  emr = self.__pat.get_emr()
    1231                  self._NB_soap_editors.save_current_editor (
    1232                          emr = emr,
    1233                          episode_name_candidates = [
    1234                                  gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
    1235                                  gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
    1236                          ]
    1237                  )
    1238                  event.Skip()
    1239          #--------------------------------------------------------
    1240 -        def _on_save_note_under_button_pressed(self, event):
    1241                  encounters = gmEMRStructWidgets.select_encounters (
    1242                          parent = self,
    1243                          patient = self.__pat,
    1244                          single_selection = True
    1245                  )
    1246                  # cancelled:
    1247                  if encounters is None:
    1248                          return
    1249                  if len(encounters) == 0:
    1250                          return
    1251
    1252                  emr = self.__pat.get_emr()
    1253                  self._NB_soap_editors.save_current_editor (
    1254                          emr = emr,
    1255                          encounter = encounter['pk_encounter'],
    1256                          episode_name_candidates = [
    1257                                  gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
    1258                                  gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
    1259                          ]
    1260                  )
    1261                  event.Skip()
    1262          #--------------------------------------------------------
    1263 -        def _on_image_button_pressed(self, event):
    1264                  emr = self.__pat.get_emr()
    1265                  self._NB_soap_editors.add_visual_progress_note_to_current_problem()
    1266                  event.Skip()
    1267          #--------------------------------------------------------
    1268          # encounter specific buttons
    1269          #--------------------------------------------------------
    1270 -        def _on_save_encounter_button_pressed(self, event):
    1271                  self.save_encounter()
    1272                  event.Skip()
    1273          #--------------------------------------------------------
    1274          # other buttons
    1275          #--------------------------------------------------------
    1276 -        def _on_save_all_button_pressed(self, event):
    1277                  self.save_encounter()
    1278                  time.sleep(0.3)
    1279                  event.Skip()
    1280                  wx.SafeYield()
    1281
    1282                  wx.CallAfter(self._save_all_button_pressed_bottom_half)
    1283                  wx.SafeYield()
    1284          #--------------------------------------------------------
    1285 -        def _save_all_button_pressed_bottom_half(self):
    1286                  emr = self.__pat.get_emr()
    1287                  saved = self._NB_soap_editors.save_all_editors (
    1288                          emr = emr,
    1289                          episode_name_candidates = [
    1290                                  gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
    1291                                  gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
    1292                          ]
    1293                  )
    1294                  if not saved:
    1295                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
    1296          #--------------------------------------------------------
    1297          # reget mixin API
    1298          #--------------------------------------------------------
    1299 -        def _populate_with_data(self):
    1300                  self.__refresh_problem_list()
    1301                  self.__refresh_encounter()
    1302                  self.__setup_initial_patient_editors()
    1303                  return True
    1304  #============================================================
    1305 -class cSoapNoteInputNotebook(wx.Notebook):
    1306          """A notebook holding panels with progress note editors.
    1307
    1308          There can be one or several progress note editor panel
    1309          for each episode being worked on. The editor class in
    1310          each panel is configurable.
    1311
    1312          There will always be one open editor.
    1313          """
    1314 -        def __init__(self, *args, **kwargs):
    1315
    1316                  kwargs['style'] = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER
    1317
    1318                  wx.Notebook.__init__(self, *args, **kwargs)
    1319          #--------------------------------------------------------
    1320          # public API
    1321          #--------------------------------------------------------
    1322 -        def add_editor(self, problem=None, allow_same_problem=False):
    1323                  """Add a progress note editor page.
    1324
    1325                  The way <allow_same_problem> is currently used in callers
    1326                  it only applies to unassociated episodes.
    1327                  """
    1328                  problem_to_add = problem
    1329
    1330                  # determine label
    1331                  if problem_to_add is None:
    1332                          label = _('new problem')
    1333                  else:
    1334                          # normalize problem type
    1335                          if isinstance(problem_to_add, gmEMRStructItems.cEpisode):
    1336                                  problem_to_add = gmEMRStructItems.episode2problem(episode = problem_to_add)
    1337
    1338                          elif isinstance(problem_to_add, gmEMRStructItems.cHealthIssue):
    1339                                  problem_to_add = gmEMRStructItems.health_issue2problem(episode = problem_to_add)
    1340
    1341                          if not isinstance(problem_to_add, gmEMRStructItems.cProblem):
    1342                                  raise TypeError('cannot open progress note editor for [%s]' % problem_to_add)
    1343
    1344                          label = problem_to_add['problem']
    1345                          # FIXME: configure maximum length
    1346                          if len(label) > 23:
    1347                                  label = label[:21] + gmTools.u_ellipsis
    1348
    1349                  # new unassociated problem or dupes allowed
    1350                  if (problem_to_add is None) or allow_same_problem:
    1351                          new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add)
    1352                          result = self.AddPage (
    1353                                  page = new_page,
    1354                                  text = label,
    1355                                  select = True
    1356                          )
    1357                          return result
    1358
    1359                  # real problem, no dupes allowed
    1360                  # - raise existing editor
    1361                  for page_idx in range(self.GetPageCount()):
    1362                          page = self.GetPage(page_idx)
    1363
    1364                          # editor is for unassociated new problem
    1365                          if page.problem is None:
    1366                                  continue
    1367
    1368                          # editor is for episode
    1369                          if page.problem['type'] == 'episode':
    1370                                  if page.problem['pk_episode'] == problem_to_add['pk_episode']:
    1371                                          self.SetSelection(page_idx)
    1372                                          gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True)
    1373                                          return True
    1374                                  continue
    1375
    1376                          # editor is for health issue
    1377                          if page.problem['type'] == 'issue':
    1378                                  if page.problem['pk_health_issue'] == problem_to_add['pk_health_issue']:
    1379                                          self.SetSelection(page_idx)
    1380                                          gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True)
    1381                                          return True
    1382                                  continue
    1383
    1384                  # - or add new editor
    1385                  new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add)
    1386                  result = self.AddPage (
    1387                          page = new_page,
    1388                          text = label,
    1389                          select = True
    1390                  )
    1391
    1392                  return result
    1393          #--------------------------------------------------------
    1394 -        def close_current_editor(self):
    1395
    1396                  page_idx = self.GetSelection()
    1397                  page = self.GetPage(page_idx)
    1398
    1399                  if not page.empty:
    1400                          really_discard = gmGuiHelpers.gm_show_question (
    1401                                  _('Are you sure you really want to\n'
    1402                                    'discard this progress note ?\n'
    1403                                  ),
    1404                                  _('Discarding progress note')
    1405                          )
    1406                          if really_discard is False:
    1407                                  return
    1408
    1409                  self.DeletePage(page_idx)
    1410
    1411                  # always keep one unassociated editor open
    1412                  if self.GetPageCount() == 0:
    1413                          self.add_editor()
    1414          #--------------------------------------------------------
    1415 -        def save_current_editor(self, emr=None, episode_name_candidates=None, encounter=None):
    1416
    1417                  page_idx = self.GetSelection()
    1418                  page = self.GetPage(page_idx)
    1419
    1420                  if not page.save(emr = emr, episode_name_candidates = episode_name_candidates, encounter = encounter):
    1421                          return
    1422
    1423                  self.DeletePage(page_idx)
    1424
    1425                  # always keep one unassociated editor open
    1426                  if self.GetPageCount() == 0:
    1427                          self.add_editor()
    1428          #--------------------------------------------------------
    1429 -        def warn_on_unsaved_soap(self):
    1430                  for page_idx in range(self.GetPageCount()):
    1431                          page = self.GetPage(page_idx)
    1432                          if page.empty:
    1433                                  continue
    1434
    1435                          gmGuiHelpers.gm_show_warning (
    1436                                  _('There are unsaved progress notes !\n'),
    1437                                  _('Unsaved progress notes')
    1438                          )
    1439                          return False
    1440
    1441                  return True
    1442          #--------------------------------------------------------
    1443 -        def save_all_editors(self, emr=None, episode_name_candidates=None):
    1444
    1445                  _log.debug('saving editors: %s', self.GetPageCount())
    1446
    1447                  all_closed = True
    1448                  for page_idx in range((self.GetPageCount() - 1), -1, -1):
    1449                          _log.debug('#%s of %s', page_idx, self.GetPageCount())
    1450                          try:
    1451                                  self.ChangeSelection(page_idx)
    1452                                  _log.debug('editor raised')
    1453                          except:
    1454                                  _log.exception('cannot raise editor')
    1455                          page = self.GetPage(page_idx)
    1456                          if page.save(emr = emr, episode_name_candidates = episode_name_candidates):
    1457                                  _log.debug('saved, deleting now')
    1458                                  self.DeletePage(page_idx)
    1459                          else:
    1460                                  _log.debug('not saved, not deleting')
    1461                                  all_closed = False
    1462
    1463                  # always keep one unassociated editor open
    1464                  if self.GetPageCount() == 0:
    1465                          self.add_editor()
    1466
    1467                  return (all_closed is True)
    1468          #--------------------------------------------------------
    1469 -        def clear_current_editor(self):
    1470                  page_idx = self.GetSelection()
    1471                  page = self.GetPage(page_idx)
    1472                  page.clear()
    1473          #--------------------------------------------------------
    1474 -        def get_current_problem(self):
    1475                  page_idx = self.GetSelection()
    1476                  page = self.GetPage(page_idx)
    1477                  return page.problem
    1478          #--------------------------------------------------------
    1479 -        def refresh_current_editor(self):
    1480                  page_idx = self.GetSelection()
    1481                  page = self.GetPage(page_idx)
    1482                  page.refresh()
    1483          #--------------------------------------------------------
    1484 -        def add_visual_progress_note_to_current_problem(self):
    1485                  page_idx = self.GetSelection()
    1486                  page = self.GetPage(page_idx)
    1487                  page.add_visual_progress_note()
    1488  #============================================================
    1489  from Gnumed.wxGladeWidgets import wxgSoapNoteExpandoEditAreaPnl
    1490
    1491 -class cSoapNoteExpandoEditAreaPnl(wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl):
    1492          """An Edit Area like panel for entering progress notes.
    1493
    1494          Subjective:                                     Codes:
    1495                  expando text ctrl
    1496          Objective:                                      Codes:
    1497                  expando text ctrl
    1498          Assessment:                                     Codes:
    1499                  expando text ctrl
    1500          Plan:                                           Codes:
    1501                  expando text ctrl
    1502          visual progress notes
    1503                  panel with images
    1504          Episode synopsis:                       Codes:
    1505                  text ctrl
    1506
    1507          - knows the problem this edit area is about
    1508          - can deal with issue or episode type problems
    1509          """
    1510
    1511 -        def __init__(self, *args, **kwargs):
    1512
    1513                  try:
    1514                          self.problem = kwargs['problem']
    1515                          del kwargs['problem']
    1516                  except KeyError:
    1517                          self.problem = None
    1518
    1519                  wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl.__init__(self, *args, **kwargs)
    1520
    1521                  self.soap_fields = [
    1522                          self._TCTRL_Soap,
    1523                          self._TCTRL_sOap,
    1524                          self._TCTRL_soAp,
    1525                          self._TCTRL_soaP
    1526                  ]
    1527
    1528                  self.__init_ui()
    1529                  self.__register_interests()
    1530          #--------------------------------------------------------
    1531 -        def __init_ui(self):
    1532                  self.refresh_summary()
    1533                  if self.problem is not None:
    1534                          if self.problem['summary'] is None:
    1535                                  self._TCTRL_episode_summary.SetValue(u'')
    1536                  self.refresh_visual_soap()
    1537          #--------------------------------------------------------
    1538 -        def refresh(self):
    1539                  self.refresh_summary()
    1540                  self.refresh_visual_soap()
    1541          #--------------------------------------------------------
    1542 -        def refresh_summary(self):
    1543                  self._TCTRL_episode_summary.SetValue(u'')
    1544                  self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([]))
    1545                  self._LBL_summary.SetLabel(_('Episode synopsis'))
    1546
    1547                  # new problem ?
    1548                  if self.problem is None:
    1549                          return
    1550
    1551                  # issue-level problem ?
    1552                  if self.problem['type'] == u'issue':
    1553                          return
    1554
    1555                  # episode-level problem
    1556                  caption = _(u'Synopsis (%s)') % (
    1557                          gmDateTime.pydt_strftime (
    1558                                  self.problem['modified_when'],
    1559                                  format = '%B %Y',
    1560                                  accuracy = gmDateTime.acc_days
    1561                          )
    1562                  )
    1563                  self._LBL_summary.SetLabel(caption)
    1564
    1565                  if self.problem['summary'] is not None:
    1566                          self._TCTRL_episode_summary.SetValue(self.problem['summary'].strip())
    1567
    1568                  val, data = self._PRW_episode_codes.generic_linked_codes2item_dict(self.problem.generic_codes)
    1569                  self._PRW_episode_codes.SetText(val, data)
    1570          #--------------------------------------------------------
    1571 -        def refresh_visual_soap(self):
    1572                  if self.problem is None:
    1573                          self._PNL_visual_soap.refresh(document_folder = None)
    1574                          return
    1575
    1576                  if self.problem['type'] == u'issue':
    1577                          self._PNL_visual_soap.refresh(document_folder = None)
    1578                          return
    1579
    1580                  if self.problem['type'] == u'episode':
    1581                          pat = gmPerson.gmCurrentPatient()
    1582                          doc_folder = pat.get_document_folder()
    1583                          emr = pat.get_emr()
    1584                          self._PNL_visual_soap.refresh (
    1585                                  document_folder = doc_folder,
    1586                                  episodes = [self.problem['pk_episode']],
    1587                                  encounter = emr.active_encounter['pk_encounter']
    1588                          )
    1589                          return
    1590          #--------------------------------------------------------
    1591 -        def clear(self):
    1592                  for field in self.soap_fields:
    1593                          field.SetValue(u'')
    1594                  self._TCTRL_episode_summary.SetValue(u'')
    1595                  self._LBL_summary.SetLabel(_('Episode synopsis'))
    1596                  self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([]))
    1597                  self._PNL_visual_soap.clear()
    1598          #--------------------------------------------------------
    1599 -        def add_visual_progress_note(self):
    1600                  fname, discard_unmodified = select_visual_progress_note_template(parent = self)
    1601                  if fname is None:
    1602                          return False
    1603
    1604                  if self.problem is None:
    1605                          issue = None
    1606                          episode = None
    1607                  elif self.problem['type'] == 'issue':
    1608                          issue = self.problem['pk_health_issue']
    1609                          episode = None
    1610                  else:
    1611                          issue = self.problem['pk_health_issue']
    1612                          episode = gmEMRStructItems.problem2episode(self.problem)
    1613
    1614                  wx.CallAfter (
    1615                          edit_visual_progress_note,
    1616                          filename = fname,
    1617                          episode = episode,
    1618                          discard_unmodified = discard_unmodified,
    1619                          health_issue = issue
    1620                  )
    1621          #--------------------------------------------------------
    1622 -        def save(self, emr=None, episode_name_candidates=None, encounter=None):
    1623
    1624                  if self.empty:
    1625                          return True
    1626
    1627                  # new episode (standalone=unassociated or new-in-issue)
    1628                  if (self.problem is None) or (self.problem['type'] == 'issue'):
    1629                          episode = self.__create_new_episode(emr = emr, episode_name_candidates = episode_name_candidates)
    1630                          # user cancelled
    1631                          if episode is None:
    1632                                  return False
    1633                  # existing episode
    1634                  else:
    1635                          episode = emr.problem2episode(self.problem)
    1636
    1637                  if encounter is None:
    1638                          encounter = emr.current_encounter['pk_encounter']
    1639
    1640                  soap_notes = []
    1641                  for note in self.soap:
    1642                          saved, data = gmClinNarrative.create_clin_narrative (
    1643                                  soap_cat = note[0],
    1644                                  narrative = note[1],
    1645                                  episode_id = episode['pk_episode'],
    1646                                  encounter_id = encounter
    1647                          )
    1648                          if saved:
    1649                                  soap_notes.append(data)
    1650
    1651                  # codes per narrative !
    1652  #               for note in soap_notes:
    1653  #                       if note['soap_cat'] == u's':
    1654  #                               codes = self._PRW_Soap_codes
    1655  #                       elif note['soap_cat'] == u'o':
    1656  #                       elif note['soap_cat'] == u'a':
    1657  #                       elif note['soap_cat'] == u'p':
    1658
    1659                  # set summary but only if not already set above for a
    1660                  # newly created episode (either standalone or within
    1661                  # a health issue)
    1662                  if self.problem is not None:
    1663                          if self.problem['type'] == 'episode':
    1664                                  episode['summary'] = self._TCTRL_episode_summary.GetValue().strip()
    1665                                  episode.save()
    1666
    1667                  # codes for episode
    1668                  episode.generic_codes = [ d['data'] for d in self._PRW_episode_codes.GetData() ]
    1669
    1670                  return True
    1671          #--------------------------------------------------------
    1672          # internal helpers
    1673          #--------------------------------------------------------
    1674 -        def __create_new_episode(self, emr=None, episode_name_candidates=None):
    1675
    1676                  episode_name_candidates.append(self._TCTRL_episode_summary.GetValue().strip())
    1677                  for candidate in episode_name_candidates:
    1678                          if candidate is None:
    1679                                  continue
    1680                          epi_name = candidate.strip().replace('\r', '//').replace('\n', '//')
    1681                          break
    1682
    1683                  dlg = wx.TextEntryDialog (
    1684                          parent = self,
    1685                          message = _('Enter a short working name for this new problem:'),
    1686                          caption = _('Creating a problem (episode) to save the notelet under ...'),
    1687                          defaultValue = epi_name,
    1688                          style = wx.OK | wx.CANCEL | wx.CENTRE
    1689                  )
    1690                  decision = dlg.ShowModal()
    1691                  if decision != wx.ID_OK:
    1692                          return None
    1693
    1694                  epi_name = dlg.GetValue().strip()
    1695                  if epi_name == u'':
    1696                          gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note'))
    1697                          return None
    1698
    1699                  # create episode
    1700                  new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True)
    1701                  new_episode['summary'] = self._TCTRL_episode_summary.GetValue().strip()
    1702                  new_episode.save()
    1703
    1704                  if self.problem is not None:
    1705                          issue = emr.problem2issue(self.problem)
    1706                          if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True):
    1707                                  gmGuiHelpers.gm_show_warning (
    1708                                          _(
    1709                                                  'The new episode:\n'
    1710                                                  '\n'
    1711                                                  ' "%s"\n'
    1712                                                  '\n'
    1713                                                  'will remain unassociated despite the editor\n'
    1714                                                  'having been invoked from the health issue:\n'
    1715                                                  '\n'
    1716                                                  ' "%s"'
    1717                                          ) % (
    1718                                                  new_episode['description'],
    1719                                                  issue['description']
    1720                                          ),
    1721                                          _('saving progress note')
    1722                                  )
    1723
    1724                  return new_episode
    1725          #--------------------------------------------------------
    1726          # event handling
    1727          #--------------------------------------------------------
    1728 -        def __register_interests(self):
    1729                  for field in self.soap_fields:
    1730                          wx_expando.EVT_ETC_LAYOUT_NEEDED(field, field.GetId(), self._on_expando_needs_layout)
    1731                  wx_expando.EVT_ETC_LAYOUT_NEEDED(self._TCTRL_episode_summary, self._TCTRL_episode_summary.GetId(), self._on_expando_needs_layout)
    1732          #--------------------------------------------------------
    1733 -        def _on_expando_needs_layout(self, evt):
    1734                  # need to tell ourselves to re-Layout to refresh scroll bars
    1735
    1736                  # provoke adding scrollbar if needed
    1737                  #self.Fit()                             # works on Linux but not on Windows
    1738                  self.FitInside()                # needed on Windows rather than self.Fit()
    1739
    1740                  if self.HasScrollbar(wx.VERTICAL):
    1741                          # scroll panel to show cursor
    1742                          expando = self.FindWindowById(evt.GetId())
    1743                          y_expando = expando.GetPositionTuple()[1]
    1744                          h_expando = expando.GetSizeTuple()[1]
    1745                          line_cursor = expando.PositionToXY(expando.GetInsertionPoint())[1] + 1
    1746                          if expando.NumberOfLines == 0:
    1747                                  no_of_lines = 1
    1748                          else:
    1749                                  no_of_lines = expando.NumberOfLines
    1750                          y_cursor = int(round((float(line_cursor) / no_of_lines) * h_expando))
    1751                          y_desired_visible = y_expando + y_cursor
    1752
    1753                          y_view = self.ViewStart[1]
    1754                          h_view = self.GetClientSizeTuple()[1]
    1755
    1756  #                       print "expando:", y_expando, "->", h_expando, ", lines:", expando.NumberOfLines
    1757  #                       print "cursor :", y_cursor, "at line", line_cursor, ", insertion point:", expando.GetInsertionPoint()
    1758  #                       print "wanted :", y_desired_visible
    1759  #                       print "view-y :", y_view
    1760  #                       print "scroll2:", h_view
    1761
    1762                          # expando starts before view
    1763                          if y_desired_visible < y_view:
    1764  #                               print "need to scroll up"
    1765                                  self.Scroll(0, y_desired_visible)
    1766
    1767                          if y_desired_visible > h_view:
    1768  #                               print "need to scroll down"
    1769                                  self.Scroll(0, y_desired_visible)
    1770          #--------------------------------------------------------
    1771          # properties
    1772          #--------------------------------------------------------
    1773 -        def _get_soap(self):
    1774                  soap_notes = []
    1775
    1776                  tmp = self._TCTRL_Soap.GetValue().strip()
    1777                  if tmp != u'':
    1778                          soap_notes.append(['s', tmp])
    1779
    1780                  tmp = self._TCTRL_sOap.GetValue().strip()
    1781                  if tmp != u'':
    1782                          soap_notes.append(['o', tmp])
    1783
    1784                  tmp = self._TCTRL_soAp.GetValue().strip()
    1785                  if tmp != u'':
    1786                          soap_notes.append(['a', tmp])
    1787
    1788                  tmp = self._TCTRL_soaP.GetValue().strip()
    1789                  if tmp != u'':
    1790                          soap_notes.append(['p', tmp])
    1791
    1792                  return soap_notes
    1793
    1794          soap = property(_get_soap, lambda x:x)
    1795          #--------------------------------------------------------
    1796 -        def _get_empty(self):
    1797
    1798                  # soap fields
    1799                  for field in self.soap_fields:
    1800                          if field.GetValue().strip() != u'':
    1801                                  return False
    1802
    1803                  # summary
    1804                  summary = self._TCTRL_episode_summary.GetValue().strip()
    1805                  if self.problem is None:
    1806                          if summary != u'':
    1807                                  return False
    1808                  elif self.problem['type'] == u'issue':
    1809                          if summary != u'':
    1810                                  return False
    1811                  else:
    1812                          if self.problem['summary'] is None:
    1813                                  if summary != u'':
    1814                                          return False
    1815                          else:
    1816                                  if summary != self.problem['summary'].strip():
    1817                                          return False
    1818
    1819                  # codes
    1820                  new_codes = self._PRW_episode_codes.GetData()
    1821                  if self.problem is None:
    1822                          if len(new_codes) > 0:
    1823                                  return False
    1824                  elif self.problem['type'] == u'issue':
    1825                          if len(new_codes) > 0:
    1826                                  return False
    1827                  else:
    1828                          old_code_pks = self.problem.generic_codes
    1829                          if len(old_code_pks) != len(new_codes):
    1830                                  return False
    1831                          for code in new_codes:
    1832                                  if code['data'] not in old_code_pks:
    1833                                          return False
    1834
    1835                  return True
    1836
    1837          empty = property(_get_empty, lambda x:x)
    1838  #============================================================
    1839 -class cSoapLineTextCtrl(wx_expando.ExpandoTextCtrl):
    1840
    1841 -        def __init__(self, *args, **kwargs):
    1842
    1843                  wx_expando.ExpandoTextCtrl.__init__(self, *args, **kwargs)
    1844
    1845                  self.__keyword_separators = regex.compile("[!?'\".,:;)}\]\r\n\s\t]+")
    1846
    1847                  self.__register_interests()
    1848          #------------------------------------------------
    1849          # fixup errors in platform expando.py
    1850          #------------------------------------------------
    1851 -        def _wrapLine(self, line, dc, width):
    1852
    1853                  if (wx.MAJOR_VERSION >= 2) and (wx.MINOR_VERSION > 8):
    1854                          return super(cSoapLineTextCtrl, self)._wrapLine(line, dc, width)
    1855
    1856                  # THIS FIX LIFTED FROM TRUNK IN SVN:
    1857                  # Estimate where the control will wrap the lines and
    1858                  # return the count of extra lines needed.
    1859                  pte = dc.GetPartialTextExtents(line)
    1860                  width -= wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
    1861                  idx = 0
    1862                  start = 0
    1863                  count = 0
    1864                  spc = -1
    1865                  while idx < len(pte):
    1866                      if line[idx] == ' ':
    1867                          spc = idx
    1868                      if pte[idx] - start > width:
    1869                          # we've reached the max width, add a new line
    1870                          count += 1
    1871                          # did we see a space? if so restart the count at that pos
    1872                          if spc != -1:
    1873                              idx = spc + 1
    1874                              spc = -1
    1875                          if idx < len(pte):
    1876                              start = pte[idx]
    1877                      else:
    1878                          idx += 1
    1879                  return count
    1880          #------------------------------------------------
    1881          # event handling
    1882          #------------------------------------------------
    1883 -        def __register_interests(self):
    1884                  #wx.EVT_KEY_DOWN (self, self.__on_key_down)
    1885                  #wx.EVT_KEY_UP (self, self.__OnKeyUp)
    1886                  wx.EVT_CHAR(self, self.__on_char)
    1887                  wx.EVT_SET_FOCUS(self, self.__on_focus)
    1888          #--------------------------------------------------------
    1889 -        def __on_focus(self, evt):
    1890                  evt.Skip()
    1891                  wx.CallAfter(self._after_on_focus)
    1892          #--------------------------------------------------------
    1893 -        def _after_on_focus(self):
    1894                  #wx.CallAfter(self._adjustCtrl)
    1895                  evt = wx.PyCommandEvent(wx_expando.wxEVT_ETC_LAYOUT_NEEDED, self.GetId())
    1896                  evt.SetEventObject(self)
    1897                  #evt.height = None
    1898                  #evt.numLines = None
    1899                  #evt.height = self.GetSize().height
    1900                  #evt.numLines = self.GetNumberOfLines()
    1901                  self.GetEventHandler().ProcessEvent(evt)
    1902          #--------------------------------------------------------
    1903 -        def __on_char(self, evt):
    1904                  char = unichr(evt.GetUnicodeKey())
    1905
    1906                  if self.LastPosition == 1:
    1907                          evt.Skip()
    1908                          return
    1909
    1910                  explicit_expansion = False
    1911                  if evt.GetModifiers() == (wx.MOD_CMD | wx.MOD_ALT): # portable CTRL-ALT-...
    1912                          if evt.GetKeyCode() != 13:
    1913                                  evt.Skip()
    1914                                  return
    1915                          explicit_expansion = True
    1916
    1917                  if not explicit_expansion:
    1918                          if self.__keyword_separators.match(char) is None:
    1919                                  evt.Skip()
    1920                                  return
    1921
    1922                  caret_pos, line_no = self.PositionToXY(self.InsertionPoint)
    1923                  line = self.GetLineText(line_no)
    1924                  word = self.__keyword_separators.split(line[:caret_pos])[-1]
    1925
    1926                  if (
    1927                          (not explicit_expansion)
    1928                                  and
    1929                          (word != u'$$steffi')                   # Easter Egg ;-)
    1930                                  and
    1931                          (word not in [ r[0] for r in gmPG2.get_text_expansion_keywords() ])
    1932                  ):
    1933                          evt.Skip()
    1934                          return
    1935
    1936                  start = self.InsertionPoint - len(word)
    1937                  wx.CallAfter(self.replace_keyword_with_expansion, word, start, explicit_expansion)
    1938
    1939                  evt.Skip()
    1940                  return
    1941          #------------------------------------------------
    1942 -        def replace_keyword_with_expansion(self, keyword=None, position=None, show_list=False):
    1943
    1944                  if show_list:
    1945                          candidates = gmPG2.get_keyword_expansion_candidates(keyword = keyword)
    1946                          if len(candidates) == 0:
    1947                                  return
    1948                          if len(candidates) == 1:
    1949                                  keyword = candidates[0]
    1950                          else:
    1951                                  keyword = gmListWidgets.get_choices_from_list (
    1952                                          parent = self,
    1953                                          msg = _(
    1954                                                  'Several macros match the keyword [%s].\n'
    1955                                                  '\n'
    1956                                                  'Please select the expansion you want to happen.'
    1957                                          ) % keyword,
    1958                                          caption = _('Selecting text macro'),
    1959                                          choices = candidates,
    1960                                          columns = [_('Keyword')],
    1961                                          single_selection = True,
    1962                                          can_return_empty = False
    1963                                  )
    1964                                  if keyword is None:
    1965                                          return
    1966
    1967                  expansion = gmPG2.expand_keyword(keyword = keyword)
    1968
    1969                  if expansion is None:
    1970                          return
    1971
    1972                  if expansion == u'':
    1973                          return
    1974
    1975                  self.Replace (
    1976                          position,
    1977                          position + len(keyword),
    1978                          expansion
    1979                  )
    1980
    1981                  self.SetInsertionPoint(position + len(expansion) + 1)
    1982                  self.ShowPosition(position + len(expansion) + 1)
    1983
    1984                  return
    1985  #============================================================
    1986  # visual progress notes
    1987  #============================================================
    1988 -def configure_visual_progress_note_editor():
    1989
    1990          def is_valid(value):
    1991
    1992                  if value is None:
    1993                          gmDispatcher.send (
    1994                                  signal = 'statustext',
    1995                                  msg = _('You need to actually set an editor.'),
    1996                                  beep = True
    1997                          )
    1998                          return False, value
    1999
    2000                  if value.strip() == u'':
    2001                          gmDispatcher.send (
    2002                                  signal = 'statustext',
    2003                                  msg = _('You need to actually set an editor.'),
    2004                                  beep = True
    2005                          )
    2006                          return False, value
    2007
    2008                  found, binary = gmShellAPI.detect_external_binary(value)
    2009                  if not found:
    2010                          gmDispatcher.send (
    2011                                  signal = 'statustext',
    2012                                  msg = _('The command [%s] is not found.') % value,
    2013                                  beep = True
    2014                          )
    2015                          return True, value
    2016
    2017                  return True, binary
    2018          #------------------------------------------
    2019          cmd = gmCfgWidgets.configure_string_option (
    2020                  message = _(
    2021                          'Enter the shell command with which to start\n'
    2022                          'the image editor for visual progress notes.\n'
    2023                          '\n'
    2024                          'Any "%(img)s" included with the arguments\n'
    2025                          'will be replaced by the file name of the\n'
    2026                          'note template.'
    2027                  ),
    2028                  option = u'external.tools.visual_soap_editor_cmd',
    2029                  bias = 'user',
    2030                  default_value = None,
    2031                  validator = is_valid
    2032          )
    2033
    2034          return cmd
    2035  #============================================================
    2036 -def select_file_as_visual_progress_note_template(parent=None):
    2037          if parent is None:
    2038                  parent = wx.GetApp().GetTopWindow()
    2039
    2040          dlg = wx.FileDialog (
    2041                  parent = parent,
    2042                  message = _('Choose file to use as template for new visual progress note'),
    2043                  defaultDir = os.path.expanduser('~'),
    2044                  defaultFile = '',
    2045                  #wildcard = "%s (*)|*|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')),
    2046                  style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST
    2047          )
    2048          result = dlg.ShowModal()
    2049
    2050          if result == wx.ID_CANCEL:
    2051                  dlg.Destroy()
    2052                  return None
    2053
    2054          full_filename = dlg.GetPath()
    2055          dlg.Hide()
    2056          dlg.Destroy()
    2057          return full_filename
    2058  #------------------------------------------------------------
    2059 -def select_visual_progress_note_template(parent=None):
    2060
    2061          if parent is None:
    2062                  parent = wx.GetApp().GetTopWindow()
    2063
    2064          dlg = gmGuiHelpers.c3ButtonQuestionDlg (
    2065                  parent,
    2066                  -1,
    2067                  caption = _('Visual progress note source'),
    2068                  question = _('From which source do you want to pick the image template ?'),
    2069                  button_defs = [
    2070                          {'label': _('Database'), 'tooltip': _('List of templates in the database.'), 'default': True},
    2071                          {'label': _('File'), 'tooltip': _('Files in the filesystem.'), 'default': False},
    2072                          {'label': _('Device'), 'tooltip': _('Image capture devices (scanners, cameras, etc)'), 'default': False}
    2073                  ]
    2074          )
    2075          result = dlg.ShowModal()
    2076          dlg.Destroy()
    2077
    2078          # 1) select from template
    2079          if result == wx.ID_YES:
    2080                  _log.debug('visual progress note template from: database template')
    2081                  from Gnumed.wxpython import gmFormWidgets
    2082                  template = gmFormWidgets.manage_form_templates (
    2083                          parent = parent,
    2084                          template_types = [gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE],
    2085                          active_only = True
    2086                  )
    2087                  if template is None:
    2088                          return (None, None)
    2089                  filename = template.export_to_file()
    2090                  if filename is None:
    2091                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long'])
    2092                          return (None, None)
    2093                  return (filename, True)
    2094
    2095          # 2) select from disk file
    2096          if result == wx.ID_NO:
    2097                  _log.debug('visual progress note template from: disk file')
    2098                  fname = select_file_as_visual_progress_note_template(parent = parent)
    2099                  if fname is None:
    2100                          return (None, None)
    2101                  # create a copy of the picked file -- don't modify the original
    2102                  ext = os.path.splitext(fname)[1]
    2103                  tmp_name = gmTools.get_unique_filename(suffix = ext)
    2104                  _log.debug('visual progress note from file: [%s] -> [%s]', fname, tmp_name)
    2105                  shutil.copy2(fname, tmp_name)
    2106                  return (tmp_name, False)
    2107
    2108          # 3) acquire from capture device
    2109          if result == wx.ID_CANCEL:
    2110                  _log.debug('visual progress note template from: image capture device')
    2111                  fnames = gmDocumentWidgets.acquire_images_from_capture_device(device = None, calling_window = parent)
    2112                  if fnames is None:
    2113                          return (None, None)
    2114                  if len(fnames) == 0:
    2115                          return (None, None)
    2116                  return (fnames[0], False)
    2117
    2118          _log.debug('no visual progress note template source selected')
    2119          return (None, None)
    2120  #------------------------------------------------------------
    2121 -def edit_visual_progress_note(filename=None, episode=None, discard_unmodified=False, doc_part=None, health_issue=None):
    2122          """This assumes <filename> contains an image which can be handled by the configured image editor."""
    2123
    2124          if doc_part is not None:
    2125                  filename = doc_part.export_to_file()
    2126                  if filename is None:
    2127                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.'))
    2128                          return None
    2129
    2130          dbcfg = gmCfg.cCfgSQL()
    2131          cmd = dbcfg.get2 (
    2132                  option = u'external.tools.visual_soap_editor_cmd',
    2133                  workplace = gmSurgery.gmCurrentPractice().active_workplace,
    2134                  bias = 'user'
    2135          )
    2136
    2137          if cmd is None:
    2138                  gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = False)
    2139                  cmd = configure_visual_progress_note_editor()
    2140                  if cmd is None:
    2141                          gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = True)
    2142                          return None
    2143
    2144          if u'%(img)s' in cmd:
    2145                  cmd % {u'img': filename}
    2146          else:
    2147                  cmd = u'%s %s' % (cmd, filename)
    2148
    2149          if discard_unmodified:
    2150                  original_stat = os.stat(filename)
    2151                  original_md5 = gmTools.file2md5(filename)
    2152
    2153          success = gmShellAPI.run_command_in_shell(cmd, blocking = True)
    2154          if not success:
    2155                  gmGuiHelpers.gm_show_error (
    2156                          _(
    2157                                  'There was a problem with running the editor\n'
    2158                                  'for visual progress notes.\n'
    2159                                  '\n'
    2160                                  ' [%s]\n'
    2161                                  '\n'
    2162                          ) % cmd,
    2163                          _('Editing visual progress note')
    2164                  )
    2165                  return None
    2166
    2167          try:
    2168                  open(filename, 'r').close()
    2169          except StandardError:
    2170                  _log.exception('problem accessing visual progress note file [%s]', filename)
    2171                  gmGuiHelpers.gm_show_error (
    2172                          _(
    2173                                  'There was a problem reading the visual\n'
    2174                                  'progress note from the file:\n'
    2175                                  '\n'
    2176                                  ' [%s]\n'
    2177                                  '\n'
    2178                          ) % filename,
    2179                          _('Saving visual progress note')
    2180                  )
    2181                  return None
    2182
    2183          if discard_unmodified:
    2184                  modified_stat = os.stat(filename)
    2185                  # same size ?
    2186                  if original_stat.st_size == modified_stat.st_size:
    2187                          modified_md5 = gmTools.file2md5(filename)
    2188                          # same hash ?
    2189                          if original_md5 == modified_md5:
    2190                                  _log.debug('visual progress note (template) not modified')
    2191                                  # ask user to decide
    2192                                  msg = _(
    2193                                          u'You either created a visual progress note from a template\n'
    2194                                          u'in the database (rather than from a file on disk) or you\n'
    2195                                          u'edited an existing visual progress note.\n'
    2196                                          u'\n'
    2197                                          u'The template/original was not modified at all, however.\n'
    2198                                          u'\n'
    2199                                          u'Do you still want to save the unmodified image as a\n'
    2200                                          u'visual progress note into the EMR of the patient ?\n'
    2201                                  )
    2202                                  save_unmodified = gmGuiHelpers.gm_show_question (
    2203                                          msg,
    2204                                          _('Saving visual progress note')
    2205                                  )
    2206                                  if not save_unmodified:
    2207                                          _log.debug('user discarded unmodified note')
    2208                                          return
    2209
    2210          if doc_part is not None:
    2211                  doc_part.update_data_from_file(fname = filename)
    2212                  doc_part.set_reviewed(technically_abnormal = False, clinically_relevant = True)
    2213                  return None
    2214
    2215          if not isinstance(episode, gmEMRStructItems.cEpisode):
    2216                  if episode is None:
    2217                          episode = _('visual progress notes')
    2218                  pat = gmPerson.gmCurrentPatient()
    2219                  emr = pat.get_emr()
    2220                  episode = emr.add_episode(episode_name = episode.strip(), pk_health_issue = health_issue, is_open = False)
    2221
    2222          doc = gmDocumentWidgets.save_file_as_new_document (
    2223                  filename = filename,
    2224                  document_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE,
    2225                  episode = episode,
    2226                  unlock_patient = True
    2227          )
    2228          doc.set_reviewed(technically_abnormal = False, clinically_relevant = True)
    2229
    2230          return doc
    2231  #============================================================
    2232 -class cVisualSoapTemplatePhraseWheel(gmPhraseWheel.cPhraseWheel):
    2233          """Phrasewheel to allow selection of visual SOAP template."""
    2234
    2235 -        def __init__(self, *args, **kwargs):
    2236
    2237                  gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
    2238
    2239                  query = u"""
    2240  SELECT
    2241          pk AS data,
    2242          name_short AS list_label,
    2243          name_sort AS field_label
    2244  FROM
    2245          ref.paperwork_templates
    2246  WHERE
    2247          fk_template_type = (SELECT pk FROM ref.form_types WHERE name = '%s') AND (
    2248                  name_long %%(fragment_condition)s
    2249                          OR
    2250                  name_short %%(fragment_condition)s
    2251          )
    2252  ORDER BY list_label
    2253  LIMIT 15
    2254  """     % gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE
    2255
    2256                  mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query])
    2257                  mp.setThresholds(2, 3, 5)
    2258
    2259                  self.matcher = mp
    2260                  self.selection_only = True
    2261          #--------------------------------------------------------
    2262 -        def _data2instance(self):
    2263                  if self.GetData() is None:
    2264                          return None
    2265
    2266                  return gmForms.cFormTemplate(aPK_obj = self.GetData())
    2267  #============================================================
    2268  from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
    2269
    2270 -class cVisualSoapPresenterPnl(wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl):
    2271
    2272 -        def __init__(self, *args, **kwargs):
    2273                  wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs)
    2274                  self._SZR_soap = self.GetSizer()
    2275                  self.__bitmaps = []
    2276          #--------------------------------------------------------
    2277          # external API
    2278          #--------------------------------------------------------
    2279 -        def refresh(self, document_folder=None, episodes=None, encounter=None):
    2280
    2281                  self.clear()
    2282                  if document_folder is not None:
    2283                          soap_docs = document_folder.get_visual_progress_notes(episodes = episodes, encounter = encounter)
    2284                          if len(soap_docs) > 0:
    2285                                  for soap_doc in soap_docs:
    2286                                          parts = soap_doc.parts
    2287                                          if len(parts) == 0:
    2288                                                  continue
    2289                                          part = parts[0]
    2290                                          fname = part.export_to_file()
    2291                                          if fname is None:
    2292                                                  continue
    2293
    2294                                          # create bitmap
    2295                                          img = gmGuiHelpers.file2scaled_image (
    2296                                                  filename = fname,
    2297                                                  height = 30
    2298                                          )
    2299                                          #bmp = wx.StaticBitmap(self, -1, img, style = wx.NO_BORDER)
    2300                                          bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
    2301
    2302                                          # create tooltip
    2303                                          img = gmGuiHelpers.file2scaled_image (
    2304                                                  filename = fname,
    2305                                                  height = 150
    2306                                          )
    2307                                          tip = agw_stt.SuperToolTip (
    2308                                                  u'',
    2309                                                  bodyImage = img,
    2310                                                  header = _('Created: %s') % part['date_generated'].strftime('%Y %B %d').decode(gmI18N.get_encoding()),
    2311                                                  footer = gmTools.coalesce(part['doc_comment'], u'').strip()
    2312                                          )
    2313                                          tip.SetTopGradientColor('white')
    2314                                          tip.SetMiddleGradientColor('white')
    2315                                          tip.SetBottomGradientColor('white')
    2316                                          tip.SetTarget(bmp)
    2317
    2318                                          bmp.doc_part = part
    2319                                          bmp.Bind(wx.EVT_LEFT_UP, self._on_bitmap_leftclicked)
    2320                                          # FIXME: add context menu for Delete/Clone/Add/Configure
    2321                                          self._SZR_soap.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM | wx.EXPAND, 3)
    2322                                          self.__bitmaps.append(bmp)
    2323
    2324                  self.GetParent().Layout()
    2325          #--------------------------------------------------------
    2326 -        def clear(self):
    2327                  while len(self._SZR_soap.GetChildren()) > 0:
    2328                          self._SZR_soap.Detach(0)
    2329  #               for child_idx in range(len(self._SZR_soap.GetChildren())):
    2330  #                       self._SZR_soap.Detach(child_idx)
    2331                  for bmp in self.__bitmaps:
    2332                          bmp.Destroy()
    2333                  self.__bitmaps = []
    2334          #--------------------------------------------------------
    2335 -        def _on_bitmap_leftclicked(self, evt):
    2336                  wx.CallAfter (
    2337                          edit_visual_progress_note,
    2338                          doc_part = evt.GetEventObject().doc_part,
    2339                          discard_unmodified = True
    2340                  )
    2341  #============================================================
    2342  from Gnumed.wxGladeWidgets import wxgSimpleSoapPluginPnl
    2343
    2344 -class cSimpleSoapPluginPnl(wxgSimpleSoapPluginPnl.wxgSimpleSoapPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
    2345 -        def __init__(self, *args, **kwargs):
    2346
    2347                  wxgSimpleSoapPluginPnl.wxgSimpleSoapPluginPnl.__init__(self, *args, **kwargs)
    2348                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    2349
    2350                  self.__pat = gmPerson.gmCurrentPatient()
    2351                  self.__problem = None
    2352                  self.__init_ui()
    2353                  self.__register_interests()
    2354          #-----------------------------------------------------
    2355          # internal API
    2356          #-----------------------------------------------------
    2357 -        def __init_ui(self):
    2358                  self._LCTRL_problems.set_columns(columns = [_('Problem list')])
    2359                  self._LCTRL_problems.activate_callback = self._on_problem_activated
    2360                  self._LCTRL_problems.item_tooltip_callback = self._on_get_problem_tooltip
    2361
    2362                  self._splitter_main.SetSashGravity(0.5)
    2363                  splitter_width = self._splitter_main.GetSizeTuple()[0]
    2364                  self._splitter_main.SetSashPosition(splitter_width / 2, True)
    2365          #-----------------------------------------------------
    2366 -        def __reset_ui(self):
    2367                  self._LCTRL_problems.set_string_items()
    2368                  self._TCTRL_soap_problem.SetValue(u'')
    2369                  self._TCTRL_soap.SetValue(u'')
    2370                  self._CHBOX_filter_by_problem.SetLabel(_('&Filter by problem'))
    2371                  self._TCTRL_journal.SetValue(u'')
    2372          #-----------------------------------------------------
    2373 -        def __save_soap(self):
    2374                  if not self.__pat.connected:
    2375                          return None
    2376
    2377                  if self.__problem is None:
    2378                          return None
    2379
    2380                  saved = self.__pat.emr.add_clin_narrative (
    2381                          note = self._TCTRL_soap.GetValue().strip(),
    2382                          soap_cat = u'u',
    2383                          episode = self.__problem
    2384                  )
    2385
    2386                  if saved is None:
    2387                          return False
    2388
    2389                  self._TCTRL_soap.SetValue(u'')
    2390                  self.__refresh_journal()
    2391                  return True
    2392          #-----------------------------------------------------
    2393 -        def __perhaps_save_soap(self):
    2394                  if self._TCTRL_soap.GetValue().strip() == u'':
    2395                          return True
    2396                  save_it = gmGuiHelpers.gm_show_question (
    2397                          title = _('Saving SOAP note'),
    2398                          question = _('Do you want to save the SOAP note ?')
    2399                  )
    2400                  if save_it:
    2401                          return self.__save_soap()
    2402                  return False
    2403          #-----------------------------------------------------
    2404 -        def __refresh_problem_list(self):
    2405                  self._LCTRL_problems.set_string_items()
    2406                  emr = self.__pat.get_emr()
    2407                  epis = emr.get_episodes(open_status = True)
    2408                  if len(epis) > 0:
    2409                          self._LCTRL_problems.set_string_items(items = [ u'%s%s' % (
    2410                                  e['description'],
    2411                                  gmTools.coalesce(e['health_issue'], u'', u' (%s)')
    2412                          ) for e in epis ])
    2413                          self._LCTRL_problems.set_data(epis)
    2414          #-----------------------------------------------------
    2415 -        def __refresh_journal(self):
    2416                  self._TCTRL_journal.SetValue(u'')
    2417                  epi = self._LCTRL_problems.get_selected_item_data(only_one = True)
    2418
    2419                  if epi is not None:
    2420                          self._CHBOX_filter_by_problem.SetLabel(_('&Filter by problem %s%s%s') % (
    2421                                  gmTools.u_left_double_angle_quote,
    2422                                  epi['description'],
    2423                                  gmTools.u_right_double_angle_quote
    2424                          ))
    2425                          self._CHBOX_filter_by_problem.Refresh()
    2426
    2427                  if not self._CHBOX_filter_by_problem.IsChecked():
    2428                          self._TCTRL_journal.SetValue(self.__pat.emr.format_summary(dob = self.__pat['dob']))
    2429                          return
    2430
    2431                  if epi is None:
    2432                          return
    2433
    2434                  self._TCTRL_journal.SetValue(epi.format_as_journal())
    2435          #-----------------------------------------------------
    2436          # event handling
    2437          #-----------------------------------------------------
    2438 -        def __register_interests(self):
    2439                  """Configure enabled event signals."""
    2440                  # client internal signals
    2441                  gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
    2442                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
    2443                  gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db)
    2444                  gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db)
    2445
    2446                  # synchronous signals
    2447                  self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback)
    2448                  gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
    2449          #-----------------------------------------------------
    2450 -        def _pre_selection_callback(self):
    2451                  """Another patient is about to be activated.
    2452
    2453                  Patient change will not proceed before this returns True.
    2454                  """
    2455                  if not self.__pat.connected:
    2456                          return True
    2457                  self.__perhaps_save_soap()
    2458                  self.__problem = None
    2459                  return True
    2460          #-----------------------------------------------------
    2461 -        def _pre_exit_callback(self):
    2462                  """The client is about to be shut down.
    2463
    2464                  Shutdown will not proceed before this returns.
    2465                  """
    2466                  if not self.__pat.connected:
    2467                          return
    2468                  if not self.__save_soap():
    2469                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot save SimpleNotes SOAP note.'), beep = True)
    2470                  return
    2471          #-----------------------------------------------------
    2472 -        def _on_pre_patient_selection(self):
    2473                  wx.CallAfter(self.__reset_ui)
    2474          #-----------------------------------------------------
    2475 -        def _on_post_patient_selection(self):
    2476                  wx.CallAfter(self._schedule_data_reget)
    2477          #-----------------------------------------------------
    2478 -        def _on_episode_issue_mod_db(self):
    2479                  wx.CallAfter(self._schedule_data_reget)
    2480          #-----------------------------------------------------
    2481 -        def _on_problem_activated(self, event):
    2482                  self.__perhaps_save_soap()
    2483                  epi = self._LCTRL_problems.get_selected_item_data(only_one = True)
    2484                  self._TCTRL_soap_problem.SetValue(_('Progress note: %s%s') % (
    2485                          epi['description'],
    2486                          gmTools.coalesce(epi['health_issue'], u'', u' (%s)')
    2487                  ))
    2488                  self.__problem = epi
    2489                  self._TCTRL_soap.SetValue(u'')
    2490          #-----------------------------------------------------
    2491 -        def _on_get_problem_tooltip(self, episode):
    2492                  return episode.format (
    2493                          patient = self.__pat,
    2494                          with_summary = False,
    2495                          with_codes = True,
    2496                          with_encounters = False,
    2497                          with_documents = False,
    2498                          with_hospital_stays = False,
    2499                          with_procedures = False,
    2500                          with_family_history = False,
    2501                          with_tests = False,
    2502                          with_vaccinations = False,
    2503                          with_health_issue = True
    2504                  )
    2505          #-----------------------------------------------------
    2506 -        def _on_list_item_selected(self, event):
    2507                  event.Skip()
    2508                  self.__refresh_journal()
    2509          #-----------------------------------------------------
    2510 -        def _on_filter_by_problem_checked(self, event):
    2511                  event.Skip()
    2512                  self.__refresh_journal()
    2513          #-----------------------------------------------------
    2514 -        def _on_add_problem_button_pressed(self, event):
    2515                  event.Skip()
    2516                  epi_name = wx.GetTextFromUser (
    2517                          _('Please enter a name for the new problem:'),
    2518                          caption = _('Adding a problem'),
    2519                          parent = self
    2520                  ).strip()
    2521                  if epi_name == u'':
    2522                          return
    2523                  self.__pat.emr.add_episode (
    2524                          episode_name = epi_name,
    2525                          pk_health_issue = None,
    2526                          is_open = True
    2527                  )
    2528          #-----------------------------------------------------
    2529 -        def _on_edit_problem_button_pressed(self, event):
    2530                  event.Skip()
    2531                  epi = self._LCTRL_problems.get_selected_item_data(only_one = True)
    2532                  if epi is None:
    2533                          return
    2534                  gmEMRStructWidgets.edit_episode(parent = self, episode = epi)
    2535          #-----------------------------------------------------
    2536 -        def _on_delete_problem_button_pressed(self, event):
    2537                  event.Skip()
    2538                  epi = self._LCTRL_problems.get_selected_item_data(only_one = True)
    2539                  if epi is None:
    2540                          return
    2541                  if not gmEMRStructItems.delete_episode(episode = epi):
    2542                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete problem. There is still clinical data recorded for it.'))
    2543          #-----------------------------------------------------
    2544 -        def _on_save_soap_button_pressed(self, event):
    2545                  event.Skip()
    2546                  self.__save_soap()
    2547          #-----------------------------------------------------
    2548 -        def _on_clear_soap_button_pressed(self, event):
    2549                  event.Skip()
    2550                  self._TCTRL_soap.SetValue(u'')
    2551          #-----------------------------------------------------
    2552          # reget-on-paint mixin API
    2553          #-----------------------------------------------------
    2554 -        def _populate_with_data(self):
    2555                  self.__refresh_problem_list()
    2556                  self.__refresh_journal()
    2557                  self._TCTRL_soap.SetValue(u'')
    2558                  return True
    2559
    2560  #============================================================
    2561  # main
    2562  #------------------------------------------------------------
    2563  if __name__ == '__main__':
    2564
    2565          if len(sys.argv) < 2:
    2566                  sys.exit()
    2567
    2568          if sys.argv[1] != 'test':
    2569                  sys.exit()
    2570
    2571          gmI18N.activate_locale()
    2572          gmI18N.install_domain(domain = 'gnumed')
    2573
    2574          #----------------------------------------
    2575 -        def test_select_narrative_from_episodes():
    2576                  pat = gmPersonSearch.ask_for_patient()
    2577                  gmPatSearchWidgets.set_active_patient(patient = pat)
    2578                  app = wx.PyWidgetTester(size = (200, 200))
    2579                  sels = select_narrative_from_episodes()
    2580                  print "selected:"
    2581                  for sel in sels:
    2582                          print sel
    2583          #----------------------------------------
    2584 -        def test_cSoapNoteExpandoEditAreaPnl():
    2585                  pat = gmPersonSearch.ask_for_patient()
    2586                  application = wx.PyWidgetTester(size=(800,500))
    2587                  soap_input = cSoapNoteExpandoEditAreaPnl(application.frame, -1)
    2588                  application.frame.Show(True)
    2589                  application.MainLoop()
    2590          #----------------------------------------
    2591 -        def test_cSoapPluginPnl():
    2592                  patient = gmPersonSearch.ask_for_patient()
    2593                  if patient is None:
    2594                          print "No patient. Exiting gracefully..."
    2595                          return
    2596                  gmPatSearchWidgets.set_active_patient(patient=patient)
    2597
    2598                  application = wx.PyWidgetTester(size=(800,500))
    2599                  soap_input = cSoapPluginPnl(application.frame, -1)
    2600                  application.frame.Show(True)
    2601                  soap_input._schedule_data_reget()
    2602                  application.MainLoop()
    2603          #----------------------------------------
    2604          #test_select_narrative_from_episodes()
    2605          test_cSoapNoteExpandoEditAreaPnl()
    2606          #test_cSoapPluginPnl()
    2607
    2608  #============================================================
    2609