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

# Source Code for Module Gnumed.wxpython.gmNarrativeWidgets

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