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

# Source Code for Module Gnumed.wxpython.gmSOAPWidgets

       1  """GNUmed SOAP related widgets.
       2  """
       3  #============================================================
       4  __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, K.Hilbert <Karsten.Hilbert@gmx.net>"
       5  __license__ = "GPL"
       6
       7  # std library
       8  import logging
       9
      10
      11  # 3rd party
      12  import wx
      13
      14
      15  # GNUmed
      16  from Gnumed.pycommon import gmDispatcher, gmI18N, gmExceptions, gmMatchProvider, gmTools, gmCfg
      17  from Gnumed.wxpython import gmResizingWidgets, gmPhraseWheel, gmEMRStructWidgets, gmGuiHelpers, gmRegetMixin, gmEditArea, gmPatSearchWidgets
      18  from Gnumed.business import gmPerson, gmEMRStructItems, gmSOAPimporter, gmPraxis, gmPersonSearch, gmStaff
      19
      20  _log = logging.getLogger('gm.ui')
      21
      22  #============================================================
      23 -def create_issue_popup(parent, pos, size, style, data_sink):
      24          ea = gmEMRStructWidgets.cHealthIssueEditArea (
      25                  parent,
      26                  -1,
      27                  wx.DefaultPosition,
      28                  wx.DefaultSize,
      29                  wx.NO_BORDER | wx.TAB_TRAVERSAL,
      30                  data_sink = data_sink
      31          )
      32          popup = gmEditArea.cEditAreaPopup (
      33                  parent = parent,
      34                  id = -1,
      35                  title = '',
      36                  pos = pos,
      37                  size = size,
      38                  style = style,
      39                  name = '',
      40                  edit_area = ea
      41          )
      42          return popup
      43  #============================================================
      44 -def create_vacc_popup(parent, pos, size, style, data_sink):
      45          ea = gmVaccWidgets.cVaccinationEditArea (
      46                  parent = parent,
      47                  id = -1,
      48          pos = pos,
      49                  size = size,
      50                  style = style,
      51                  data_sink = data_sink
      52          )
      53          popup = gmEditArea.cEditAreaPopup (
      54                  parent = parent,
      55                  id = -1,
      56                  title = _('Enter vaccination given'),
      57                  pos = pos,
      58                  size = size,
      59                  style = style,
      60                  name = '',
      61                  edit_area = ea
      62          )
      63          return popup
      64  #============================================================
      65  # FIXME: keywords hardcoded for now, load from cfg in backend instead
      66  progress_note_keywords = {
      67          's': {
      68                  '$missing_action': {},
      69                  'phx$': {
      70                          'widget_factory': create_issue_popup,
      71                          'widget_data_sink': None
      72                  },
      73                  'ea$:': {
      74                          'widget_factory': create_issue_popup,
      75                          'widget_data_sink': None
      76                  },
      77                  '$vacc': {
      78                          'widget_factory': create_vacc_popup,
      79                          'widget_data_sink': None
      80                  },
      81                  'impf:': {
      82                          'widget_factory': create_vacc_popup,
      83                          'widget_data_sink': None
      84                  },
      85                  'icpc:': {},
      86                  'icpc?': {}
      87          },
      88          'o': {
      89                  'icpc:': {},
      90                  'icpc?': {}
      91          },
      92          'a': {
      93                  'icpc:': {},
      94                  'icpc?': {}
      95          },
      96          'p': {
      97                  '$vacc': {
      98                          'widget_factory': create_vacc_popup,
      99                          'widget_data_sink': None
     100                  },
     101                  'icpc:': {},
     102                  'icpc?': {}
     103          }
     104  }
     105  #============================================================
     106 -class cProgressNoteInputNotebook(wx.Notebook, gmRegetMixin.cRegetOnPaintMixin):
     107          """A notebook holding panels with progress note editors.
     108
     109          There is one progress note editor panel for each episode being worked on.
     110          """
     111 -        def __init__(self, parent, id, pos=wx.DefaultPosition, size=wx.DefaultSize):
     112                  wx.Notebook.__init__ (
     113                          self,
     114                          parent = parent,
     115                          id = id,
     116                          pos = pos,
     117                          size = size,
     118                          style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
     119                          name = self.__class__.__name__
     120                  )
     121                  _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id)
     122                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
     123                  self.__pat = gmPerson.gmCurrentPatient()
     124                  self.__do_layout()
     125                  self.__register_interests()
     126          #--------------------------------------------------------
     127          # public API
     128          #--------------------------------------------------------
     129 -        def add_editor(self, problem=None, allow_same_problem=False):
     130                  """Add a progress note editor page.
     131
     132                  The way <allow_same_problem> is currently used in callers
     133                  it only applies to unassociated episodes.
     134                  """
     135                  problem_to_add = problem
     136
     137                  # determine label
     138                  if problem_to_add is None:
     139                          label = _('new problem')
     140                  else:
     141                          # normalize problem type
     142                          emr = self.__pat.emr
     143                          if isinstance(problem_to_add, gmEMRStructItems.cEpisode):
     144                                  problem_to_add = emr.episode2problem(episode = problem_to_add)
     145                          elif isinstance(problem_to_add, gmEMRStructItems.cHealthIssue):
     146                                  problem_to_add = emr.health_issue2problem(issue = problem_to_add)
     147                          if not isinstance(problem_to_add, gmEMRStructItems.cProblem):
     148                                  raise TypeError('cannot open progress note editor for [%s]' % problem_to_add)
     149                          label = problem_to_add['problem']
     150                          # FIXME: configure maximum length
     151                          if len(label) > 23:
     152                                  label = label[:21] + gmTools.u_ellipsis
     153
     154                  if allow_same_problem:
     155                          new_page = cResizingSoapPanel(parent = self, problem = problem_to_add)
     156                          result = self.AddPage (
     157                                  page = new_page,
     158                                  text = label,
     159                                  select = True
     160                          )
     161                          return result
     162
     163                  # check for dupes
     164                  # new unassociated problem
     165                  if problem_to_add is None:
     166                          # check for dupes
     167                          for page_idx in range(self.GetPageCount()):
     168                                  page = self.GetPage(page_idx)
     169                                  # found
     170                                  if page.get_problem() is None:
     171                                          self.SetSelection(page_idx)
     172                                          return True
     173                                  continue
     174                          # not found
     175                          new_page = cResizingSoapPanel(parent = self, problem = problem_to_add)
     176                          result = self.AddPage (
     177                                  page = new_page,
     178                                  text = label,
     179                                  select = True
     180                          )
     181                          return result
     182
     183                  # real problem
     184                  # - raise existing editor ?
     185                  for page_idx in range(self.GetPageCount()):
     186                          page = self.GetPage(page_idx)
     187                          problem_of_page = page.get_problem()
     188                          # editor is for unassociated new problem
     189                          if problem_of_page is None:
     190                                  continue
     191                          # editor is for episode
     192                          if problem_of_page['type'] == 'episode':
     193                                  if problem_to_add['type'] == 'issue':
     194                                          is_equal = (problem_of_page['pk_health_issue'] == problem_to_add['pk_health_issue'])
     195                                  else:
     196                                          is_equal = (problem_of_page['pk_episode'] == problem_to_add['pk_episode'])
     197                                  if is_equal:
     198                                          self.SetSelection(page_idx)
     199                                          return True
     200                                  continue
     201                          # editor is for health issue
     202                          if problem_of_page['type'] == 'issue':
     203                                  if problem_of_page['pk_health_issue'] == problem_to_add['pk_health_issue']:
     204                                          self.SetSelection(page_idx)
     205                                          return True
     206                                  continue
     207
     208                  # - add new editor
     209                  new_page = cResizingSoapPanel(parent = self, problem = problem_to_add)
     210                  result = self.AddPage (
     211                          page = new_page,
     212                          text = label,
     213                          select = True
     214                  )
     215
     216                  return result
     217          #--------------------------------------------------------
     218 -        def close_current_editor(self):
     219
     220                  page_idx = self.GetSelection()
     221                  page = self.GetPage(page_idx)
     222
     223                  if not page.editor_empty():
     224                          really_discard = gmGuiHelpers.gm_show_question (
     225                                  _('Are you sure you really want to\n'
     226                                    'discard this progress note ?\n'
     227                                  ),
     228                                  _('Discarding progress note')
     229                          )
     230                          if really_discard is False:
     231                                  return
     232
     233                  self.DeletePage(page_idx)
     234
     235                  # always keep one unassociated editor open
     236                  if self.GetPageCount() == 0:
     237                          self.add_editor()
     238          #--------------------------------------------------------
     239 -        def warn_on_unsaved_soap(self):
     240
     241                  for page_idx in range(self.GetPageCount()):
     242                          page = self.GetPage(page_idx)
     243                          if page.editor_empty():
     244                                  continue
     245
     246                          gmGuiHelpers.gm_show_warning (
     247                                  _('There are unsaved progress notes !\n'),
     248                                  _('Unsaved progress notes')
     249                          )
     250                          return False
     251
     252                  return True
     253          #--------------------------------------------------------
     254 -        def save_unsaved_soap(self):
     255                  save_all = False
     256                  dlg = None
     257                  for page_idx in range(self.GetPageCount()):
     258                          page = self.GetPage(page_idx)
     259                          if page.editor_empty():
     260                                  continue
     261
     262                          if dlg is None:
     263                                  dlg = gmGuiHelpers.c3ButtonQuestionDlg (
     264                                          self,
     265                                          -1,
     266                                          caption = _('Unsaved progress note'),
     267                                          question = _(
     268                                                  'This progress note has not been saved yet.\n'
     269                                                  '\n'
     270                                                  'Do you want to save it or discard it ?\n\n'
     271                                          ),
     272                                          button_defs = [
     273                                                  {'label': _('&Save'), 'tooltip': _('Save this progress note'), 'default': True},
     274                                                  {'label': _('&Discard'), 'tooltip': _('Discard this progress note'), 'default': False},
     275                                                  {'label': _('Save &all'), 'tooltip': _('Save all remaining unsaved progress notes'), 'default': False}
     276                                          ]
     277                                  )
     278
     279                          if not save_all:
     280                                  self.ChangeSelection(page_idx)
     281                                  decision = dlg.ShowModal()
     282                                  if decision == wx.ID_NO:
     283                                          _log.info('user requested discarding of unsaved progress note')
     284                                          continue
     285                                  if decision == wx.ID_CANCEL:
     286                                          save_all = True
     287                          page.save()
     288
     289                  if dlg is not None:
     290                          dlg.DestroyLater()
     291          #--------------------------------------------------------
     292          # internal API
     293          #--------------------------------------------------------
     294 -        def __do_layout(self):
     295                  # add one empty unassociated progress note editor - which to
     296                  # have (by all sensible accounts) seems to be the intent when
     297                  # instantiating this class
     298                  self.add_editor()
     299          #--------------------------------------------------------
     300          # reget mixin API
     301          #--------------------------------------------------------
     302 -        def _populate_with_data(self):
     303                  print('[%s._populate_with_data] nothing to do, really...' % self.__class__.__name__)
     304                  return True
     305          #--------------------------------------------------------
     306          # event handling
     307          #--------------------------------------------------------
     308 -        def __register_interests(self):
     309                  """Configure enabled event signals
     310                  """
     311                  # wxPython events
     312
     313                  # client internal signals
     314                  gmDispatcher.connect(signal = 'post_patient_selection', receiver=self._on_post_patient_selection)
     315  #               gmDispatcher.connect(signal = u'application_closing', receiver=self._on_application_closing)
     316
     317                  self.__pat.register_before_switching_from_patient_callback(callback = self._before_switching_from_patient_callback)
     318
     319                  gmDispatcher.send(signal = 'register_pre_exit_callback', callback = self._pre_exit_callback)
     320          #--------------------------------------------------------
     321 -        def _before_switching_from_patient_callback(self):
     322                  """Another patient is about to be activated.
     323
     324                  Patient change will not proceed before this returns True.
     325                  """
     326                  return self.warn_on_unsaved_soap()
     327          #--------------------------------------------------------
     328 -        def _pre_exit_callback(self):
     329                  """The client is about to be shut down.
     330
     331                  Shutdown will not proceed before this returns.
     332                  """
     333                  self.save_unsaved_soap()
     334          #--------------------------------------------------------
     335 -        def _on_post_patient_selection(self):
     336                  """Patient changed."""
     337                  self.DeleteAllPages()
     338                  self.add_editor()
     339                  self._schedule_data_reget()
     340          #--------------------------------------------------------
     341  #       def _on_application_closing(self):
     342  #               """GNUmed is shutting down."""
     343  #               print "[%s]: the application is closing down" % self.__class__.__name__
     344  #               print "************************************"
     345  #               print "need to ask user about SOAP saving !"
     346  #               print "************************************"
     347          #--------------------------------------------------------
     348  #       def _on_episodes_modified(self):
     349  #               print "[%s]: episode modified" % self.__class__.__name__
     350  #               print "need code to deal with:"
     351  #               print "- deleted episode that we show so we can notify the user"
     352  #               print "- renamed episode so we can update our episode label"
     353  #               self._schedule_data_reget()
     354  #               pass
     355  #============================================================
     356 -class cNotebookedProgressNoteInputPanel(wx.Panel):
     357          """A panel for entering multiple progress notes in context.
     358
     359          Expects to be used as a notebook page.
     360
     361          Left hand side:
     362          - problem list (health issues and active episodes)
     363
     364          Right hand side:
     365          - notebook with progress note editors
     366
     367          Listens to patient change signals, thus acts on the current patient.
     368          """
     369          #--------------------------------------------------------
     370 -        def __init__(self, parent, id):
     371                  """Contructs a new instance of SOAP input panel
     372
     373                  @param parent: Wx parent widget
     374                  @param id: Wx widget id
     375                  """
     376                  # Call parents constructors
     377                  wx.Panel.__init__ (
     378                          self,
     379                          parent = parent,
     380                          id = id,
     381                          pos = wx.DefaultPosition,
     382                          size = wx.DefaultSize,
     383                          style = wx.NO_BORDER
     384                  )
     385                  self.__pat = gmPerson.gmCurrentPatient()
     386
     387                  # ui contruction and event handling set up
     388                  self.__do_layout()
     389                  self.__register_interests()
     390                  self.reset_ui_content()
     391          #--------------------------------------------------------
     392          # public API
     393          #--------------------------------------------------------
     394 -        def reset_ui_content(self):
     395                  """
     396                  Clear all information from input panel
     397                  """
     398                  self.__LST_problems.Clear()
     399                  self.__soap_notebook.DeleteAllPages()
     400                  self.__soap_notebook.add_editor()
     401          #--------------------------------------------------------
     402          # internal helpers
     403          #--------------------------------------------------------
     404 -        def __do_layout(self):
     405                  """Arrange widgets.
     406
     407                  left: problem list (mix of issues and episodes)
     408                  right: soap editors
     409                  """
     410                  # SOAP input panel main splitter window
     411                  self.__splitter = wx.SplitterWindow(self, -1)
     412
     413                  # left hand side
     414                  PNL_list = wx.Panel(self.__splitter, -1)
     415                  # - header
     416                  list_header = wx.StaticText (
     417                          parent = PNL_list,
     418                          id = -1,
     419                          label = _('Active problems'),
     420                          style = wx.NO_BORDER | wx.ALIGN_CENTRE
     421                  )
     422                  # - problem list
     423                  self.__LST_problems = wx.ListBox (
     424                          PNL_list,
     425                          -1,
     426                          style= wx.NO_BORDER
     427                  )
     428                  # - arrange
     429                  szr_left = wx.BoxSizer(wx.VERTICAL)
     430                  szr_left.Add(list_header, 0)
     431                  szr_left.Add(self.__LST_problems, 1, wx.EXPAND)
     432                  PNL_list.SetSizerAndFit(szr_left)
     433
     434                  # right hand side
     435                  # - soap inputs panel
     436                  PNL_soap_editors = wx.Panel(self.__splitter, -1)
     437                  # - progress note notebook
     438                  self.__soap_notebook = cProgressNoteInputNotebook(PNL_soap_editors, -1)
     439                  # - buttons
     440                  self.__BTN_add_unassociated = wx.Button(PNL_soap_editors, -1, _('&New'))
     441                  tt = _(
     442                          'Add editor for a new unassociated progress note.\n\n'
     443                          'There is a configuration option whether or not to\n'
     444                          'allow several new unassociated progress notes at once.'
     445                  )
     446                  self.__BTN_add_unassociated.SetToolTip(tt)
     447
     448                  self.__BTN_save = wx.Button(PNL_soap_editors, -1, _('&Save'))
     449                  self.__BTN_save.SetToolTip(_('Save progress note into medical record and close this editor.'))
     450
     451                  self.__BTN_clear = wx.Button(PNL_soap_editors, -1, _('&Clear'))
     452                  self.__BTN_clear.SetToolTip(_('Clear this progress note editor.'))
     453
     454                  self.__BTN_discard = wx.Button(PNL_soap_editors, -1, _('&Discard'))
     455                  self.__BTN_discard.SetToolTip(_('Discard progress note and close this editor. You will loose any data already typed into this editor !'))
     456
     457                  # - arrange
     458                  szr_btns_right = wx.BoxSizer(wx.HORIZONTAL)
     459                  szr_btns_right.Add(self.__BTN_add_unassociated, 0, wx.SHAPED)
     460                  szr_btns_right.Add(self.__BTN_clear, 0, wx.SHAPED)
     461                  szr_btns_right.Add(self.__BTN_save, 0, wx.SHAPED)
     462                  szr_btns_right.Add(self.__BTN_discard, 0, wx.SHAPED)
     463
     464                  szr_right = wx.BoxSizer(wx.VERTICAL)
     465                  szr_right.Add(self.__soap_notebook, 1, wx.EXPAND)
     466                  szr_right.Add(szr_btns_right)
     467                  PNL_soap_editors.SetSizerAndFit(szr_right)
     468
     469                  # arrange widgets
     470                  self.__splitter.SetMinimumPaneSize(20)
     471                  self.__splitter.SplitVertically(PNL_list, PNL_soap_editors)
     472
     473                  szr_main = wx.BoxSizer(wx.VERTICAL)
     474                  szr_main.Add(self.__splitter, 1, wx.EXPAND, 0)
     475                  self.SetSizerAndFit(szr_main)
     476          #--------------------------------------------------------
     477 -        def __refresh_problem_list(self):
     478                  """Update health problems list.
     479                  """
     480                  self.__LST_problems.Clear()
     481                  emr = self.__pat.emr
     482                  problems = emr.get_problems()
     483                  for problem in problems:
     484                          if not problem['problem_active']:
     485                                  continue
     486                          if problem['type'] == 'issue':
     487                                  issue = emr.problem2issue(problem)
     488                                  last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue'])
     489                                  if last_encounter is None:
     490                                          last = issue['modified_when'].strftime('%m/%Y')
     491                                  else:
     492                                          last = last_encounter['last_affirmed'].strftime('%m/%Y')
     493                                  label = '%s: %s "%s"' % (last, problem['l10n_type'], problem['problem'])
     494                          elif problem['type'] == 'episode':
     495                                  epi = emr.problem2episode(problem)
     496                                  last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode'])
     497                                  if last_encounter is None:
     498                                          last = epi['episode_modified_when'].strftime('%m/%Y')
     499                                  else:
     500                                          last = last_encounter['last_affirmed'].strftime('%m/%Y')
     501                                  label = '%s: %s "%s"%s' % (
     502                                          last,
     503                                          problem['l10n_type'],
     504                                          problem['problem'],
     505                                          gmTools.coalesce(value2test = epi['health_issue'], return_instead = '', template4value = ' (%s)')
     506                                  )
     507                          self.__LST_problems.Append(label, problem)
     508                  splitter_width = self.__splitter.GetSize()[0]
     509                  self.__splitter.SetSashPosition((splitter_width / 2), True)
     510                  self.Refresh()
     511                  #self.Update()
     512                  return True
     513          #--------------------------------------------------------
     514          # event handling
     515          #--------------------------------------------------------
     516 -        def __register_interests(self):
     517                  """Configure enabled event signals
     518                  """
     519                  # wxPython events
     520                  self.__LST_problems.Bind(wx.EVT_LISTBOX_DCLICK, self.__on_problem_activated)
     521                  self.__BTN_save.Bind(wx.EVT_BUTTON, self.__on_save)
     522                  self.__BTN_clear.Bind(wx.EVT_BUTTON, self.__on_clear)
     523                  self.__BTN_discard.Bind(wx.EVT_BUTTON, self.__on_discard)
     524                  self.__BTN_add_unassociated.Bind(wx.EVT_BUTTON, self.__on_add_unassociated)
     525                  #wx.EVT_LISTBOX_DCLICK(self.__LST_problems, self.__LST_problems.GetId(), self.__on_problem_activated)
     526                  #wx.EVT_BUTTON(self.__BTN_save, self.__BTN_save.GetId(), self.__on_save)
     527                  #wx.EVT_BUTTON(self.__BTN_clear, self.__BTN_clear.GetId(), self.__on_clear)
     528                  #wx.EVT_BUTTON(self.__BTN_discard, self.__BTN_discard.GetId(), self.__on_discard)
     529                  #wx.EVT_BUTTON(self.__BTN_add_unassociated, self.__BTN_add_unassociated.GetId(), self.__on_add_unassociated)
     530
     531                  # client internal signals
     532                  gmDispatcher.connect(signal='post_patient_selection', receiver=self._on_post_patient_selection)
     533                  gmDispatcher.connect(signal = 'clin.episode_mod_db', receiver = self._on_episode_issue_mod_db)
     534                  gmDispatcher.connect(signal = 'clin.health_issue_mod_db', receiver = self._on_episode_issue_mod_db)
     535          #--------------------------------------------------------
     536 -        def _on_post_patient_selection(self):
     537                  """Patient changed."""
     538                  if self.GetParent().GetCurrentPage() == self:
     539                          self.reset_ui_content()
     540          #--------------------------------------------------------
     541 -        def _on_episode_issue_mod_db(self):
     542                  if self.GetParent().GetCurrentPage() == self:
     543                          self.__refresh_problem_list()
     544          #--------------------------------------------------------
     545 -        def __on_clear(self, event):
     546                  """Clear raised SOAP input widget.
     547                  """
     548                  soap_nb_page = self.__soap_notebook.GetPage(self.__soap_notebook.GetSelection())
     549                  soap_nb_page.Clear()
     550          #--------------------------------------------------------
     551 -        def __on_discard(self, event):
     552                  """Discard raised SOAP input widget.
     553
     554                  Will throw away data !
     555                  """
     556                  self.__soap_notebook.close_current_editor()
     557          #--------------------------------------------------------
     558 -        def __on_add_unassociated(self, evt):
     559                  """Add new editor for as-yet unassociated progress note.
     560
     561                  Clinical logic as per discussion with Jim Busser:
     562
     563                  - if patient has no episodes:
     564                          - new patient
     565                          - always allow several NEWs
     566                  - if patient has episodes:
     567                          - allow several NEWs per configuration
     568                  """
     569                  emr = self.__pat.emr
     570                  epis = emr.get_episodes()
     571
     572                  if len(epis) == 0:
     573                          value = True
     574                  else:
     575                          dbcfg = gmCfg.cCfgSQL()
     576                          value = bool(dbcfg.get2 (
     577                                  option = 'horstspace.soap_editor.allow_same_episode_multiple_times',
     578                                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     579                                  bias = 'user',
     580                                  default = False
     581                          ))
     582
     583                  self.__soap_notebook.add_editor(allow_same_problem = value)
     584          #--------------------------------------------------------
     585 -        def __on_problem_activated(self, event):
     586                  """
     587                  When the user changes health issue selection, update selected issue
     588                  reference and update buttons according its input status.
     589
     590                  when the user selects a problem in the problem list:
     591                          - check whether selection is issue or episode
     592                          - if editor for episode exists: focus it
     593                          - if no editor for episode exists: create one and focus it
     594                  """
     595                  problem_idx = self.__LST_problems.GetSelection()
     596                  problem = self.__LST_problems.GetClientData(problem_idx)
     597
     598                  if self.__soap_notebook.add_editor(problem = problem):
     599                          return True
     600
     601                  gmGuiHelpers.gm_show_error (
     602                          aMessage = _(
     603                                  'Cannot open progress note editor for\n\n'
     604                                  '[%s].\n\n'
     605                          ) % problem['problem'],
     606                          aTitle = _('opening progress note editor')
     607                  )
     608                  return False
     609          #--------------------------------------------------------
     610 -        def __on_save(self, event):
     611                  """Save data to backend and close editor.
     612                  """
     613                  page_idx = self.__soap_notebook.GetSelection()
     614                  soap_nb_page = self.__soap_notebook.GetPage(page_idx)
     615                  if not soap_nb_page.save():
     616                          gmDispatcher.send(signal='statustext', msg=_('Problem saving progress note: duplicate information ?'))
     617                          return False
     618                  self.__soap_notebook.DeletePage(page_idx)
     619                  # always keep one unassociated editor open
     620                  self.__soap_notebook.add_editor()
     621                  #self.__refresh_problem_list()
     622                  return True
     623          #--------------------------------------------------------
     624          # notebook plugin API
     625          #--------------------------------------------------------
     626 -        def repopulate_ui(self):
     627                  self.__refresh_problem_list()
     628  #============================================================
     629 -class cSOAPLineDef:
     630 -        def __init__(self):
     631                  self.label = _('label missing')
     632                  self.text = ''
     633                  self.soap_cat = _('soap cat missing')
     634                  self.is_rfe = False             # later support via types
     635                  self.data = None
     636  #============================================================
     637  # FIXME: this should be a more generic(ally named) class
     638  # FIXME: living elsewhere
     639 -class cPopupDataHolder:
     640
     641          _data_savers = {}
     642
     643 -        def __init__(self):
     644                  self.__data = {}
     645          #--------------------------------------------------------
     646 -        def store_data(self, popup_type=None, desc=None, data=None, old_desc=None):
     647                  # FIXME: do fancy validations
     648
     649                  print("storing popup data:", desc)
     650                  print("type", popup_type)
     651                  print("data", data)
     652
     653                  # verify structure
     654                  try:
     655                          self.__data[popup_type]
     656                  except KeyError:
     657                          self.__data[popup_type] = {}
     658                  # store new data
     659                  self.__data[popup_type][desc] = {
     660                          'data': data
     661                  }
     662                  # remove old data if necessary
     663                  try:
     664                          del self.__data[popup_type][old_desc]
     665                  except:
     666                          pass
     667                  return True
     668          #--------------------------------------------------------
     669 -        def save(self):
     670                  for popup_type in self.__data.keys():
     671                          try:
     672                                  saver_func = self.__data_savers[popup_type]
     673                          except KeyError:
     674                                  _log.exception('no saver for popup data type [%s] configured', popup_type)
     675                                  return False
     676                          for desc in self.__data[popup_type].keys():
     677                                  data = self.__data[popup_type][desc]['data']
     678                                  saver_func(data)
     679                  return True
     680          #--------------------------------------------------------
     681 -        def clear(self):
     682                  self.__data = {}
     683          #--------------------------------------------------------
     684  #       def remove_data(self, popup_type=None, desc=None):
     685  #               del self.__data[popup_type][desc]
     686          #--------------------------------------------------------
     687          # def get_descs(self, popup_type=None, origination_soap=None):
     688          # def get_data(self, desc=None):
     689          # def rename_data(self, old_desc=None, new_desc=None):
     690  #============================================================
     691 -class cResizingSoapWin(gmResizingWidgets.cResizingWindow):
     692
     693 -        def __init__(self, parent, size, input_defs=None, problem=None):
     694                  """Resizing SOAP note input editor.
     695
     696                  This is a wrapper around a few resizing STCs (the
     697                  labels and categories are settable) which are
     698                  customized to accept progress note input. It provides
     699                  the unified resizing behaviour.
     700
     701                  Knows how to save it's data into the backend.
     702
     703                  @param input_defs: note's labels and categories
     704                  @type input_defs: list of cSOAPLineDef instances
     705                  """
     706                  if input_defs is None or len(input_defs) == 0:
     707                          raise gmExceptions.ConstructorError('cannot generate note with field defs [%s]' % input_defs)
     708
     709                  # FIXME: *actually* this should be a session-local
     710                  # FIXME: holding store at the c_ClinicalRecord level
     711                  self.__embedded_data_holder = cPopupDataHolder()
     712
     713                  self.__input_defs = input_defs
     714
     715                  gmResizingWidgets.cResizingWindow.__init__(self, parent, id=-1, size=size)
     716
     717                  self.__problem = problem
     718                  if isinstance(problem, gmEMRStructItems.cEpisode):
     719                          self.__problem = emr.episode2problem(episode = problem)
     720                  elif isinstance(problem, gmEMRStructItems.cHealthIssue):
     721                          self.__problem = emr.health_issue2problem(issue = problem)
     722                  self.__pat = gmPerson.gmCurrentPatient()
     723          #--------------------------------------------------------
     724          # cResizingWindow API
     725          #--------------------------------------------------------
     726 -        def DoLayout(self):
     727                  """Visually display input note according to user defined labels.
     728                  """
     729                  # configure keywords
     730                  for soap_cat in progress_note_keywords.keys():
     731                          category = progress_note_keywords[soap_cat]
     732                          for kwd in category.keys():
     733                                  category[kwd]['widget_data_sink'] = self.__embedded_data_holder.store_data
     734                  input_fields = []
     735                  # add fields to edit widget
     736                  # note: this may produce identically labelled lines
     737                  for line_def in self.__input_defs:
     738                          input_field = gmResizingWidgets.cResizingSTC(self, -1, data = line_def)
     739                          input_field.SetText(line_def.text)
     740                          kwds = progress_note_keywords[line_def.soap_cat]
     741                          input_field.set_keywords(popup_keywords=kwds)
     742                          # FIXME: pending matcher setup
     743                          self.AddWidget(widget=input_field, label=line_def.label)
     744                          self.Newline()
     745                          input_fields.append(input_field)
     746                  # setup tab navigation between input fields
     747                  for field_idx in range(len(input_fields)):
     748                          # previous
     749                          try:
     750                                  input_fields[field_idx].prev_in_tab_order = input_fields[field_idx-1]
     751                          except IndexError:
     752                                  input_fields[field_idx].prev_in_tab_order = None
     753                          # next
     754                          try:
     755                                  input_fields[field_idx].next_in_tab_order = input_fields[field_idx+1]
     756                          except IndexError:
     757                                  input_fields[field_idx].next_in_tab_order = None
     758          #--------------------------------------------------------
     759          # public API
     760          #--------------------------------------------------------
     761 -        def save(self):
     762                  """Save data into backend."""
     763
     764                  # fill progress_note for import
     765                  progress_note = []
     766                  aoe = ''
     767                  rfe = ''
     768                  has_rfe = False
     769                  soap_lines_contents = self.GetValue()
     770                  for line_content in soap_lines_contents.values():
     771                          if line_content.text.strip() == '':
     772                                  continue
     773                          progress_note.append ({
     774                                  gmSOAPimporter.soap_bundle_SOAP_CAT_KEY: line_content.data.soap_cat,
     775                                  gmSOAPimporter.soap_bundle_TYPES_KEY: [],               # these types need to come from the editor
     776                                  gmSOAPimporter.soap_bundle_TEXT_KEY: line_content.text.rstrip()
     777                          })
     778                          if line_content.data.is_rfe:
     779                                  has_rfe = True
     780                                  rfe += line_content.text.rstrip()
     781                          if line_content.data.soap_cat == 'a':
     782                                  aoe += line_content.text.rstrip()
     783
     784                  emr = self.__pat.emr
     785
     786                  # - new episode, must get name from narrative (or user)
     787                  if (self.__problem is None) or (self.__problem['type'] == 'issue'):
     788                          # work out episode name
     789                          epi_name = ''
     790                          if len(aoe) != 0:
     791                                  epi_name = aoe
     792                          else:
     793                                  epi_name = rfe
     794
     795                          dlg = wx.TextEntryDialog (
     796                                  self,
     797                                  _('Enter a descriptive name for this new problem:'),
     798                                  caption = _('Creating a problem (episode) to save the notelet under ...'),
     799                                  value = epi_name.replace('\r', '//').replace('\n', '//'),
     800                                  style = wx.OK | wx.CANCEL | wx.CENTRE
     801                          )
     802                          decision = dlg.ShowModal()
     803                          if decision != wx.ID_OK:
     804                                  return False
     805
     806                          epi_name = dlg.GetValue().strip()
     807                          if epi_name == '':
     808                                  gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note'))
     809                                  return False
     810
     811                          # new unassociated episode
     812                          new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True)
     813
     814                          if self.__problem is not None:
     815                                  issue = emr.problem2issue(self.__problem)
     816                                  if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True):
     817                                          print("error moving episode to issue")
     818
     819                          epi_id = new_episode['pk_episode']
     820                  else:
     821                          epi_id = self.__problem['pk_episode']
     822
     823                  # set up clinical context in progress note
     824                  encounter = emr.active_encounter
     825                  staff_id = gmStaff.gmCurrentProvider()['pk_staff']
     826                  clin_ctx = {
     827                          gmSOAPimporter.soap_bundle_EPISODE_ID_KEY: epi_id,
     828                          gmSOAPimporter.soap_bundle_ENCOUNTER_ID_KEY: encounter['pk_encounter'],
     829                          gmSOAPimporter.soap_bundle_STAFF_ID_KEY: staff_id
     830                  }
     831                  for line in progress_note:
     832                          line[gmSOAPimporter.soap_bundle_CLIN_CTX_KEY] = clin_ctx
     833
     834                  # dump progress note to backend
     835                  importer = gmSOAPimporter.cSOAPImporter()
     836                  if not importer.import_soap(progress_note):
     837                          gmGuiHelpers.gm_show_error(_('Error saving progress note.'), _('saving progress note'))
     838                          return False
     839
     840                  # dump embedded data to backend
     841                  if not self.__embedded_data_holder.save():
     842                          gmGuiHelpers.gm_show_error (
     843                                  _('Error saving embedded data.'),
     844                                  _('saving progress note')
     845                          )
     846                          return False
     847                  self.__embedded_data_holder.clear()
     848
     849                  return True
     850          #--------------------------------------------------------
     851 -        def get_problem(self):
     852                  return self.__problem
     853          #--------------------------------------------------------
     854 -        def is_empty(self):
     855                  editor_content = self.GetValue()
     856
     857                  for field_content in editor_content.values():
     858                          if field_content.text.strip() != '':
     859                                  return False
     860
     861                  return True
     862  #============================================================
     863 -class cResizingSoapPanel(wx.Panel):
     864          """Basic progress note panel.
     865
     866          It provides a gmResizingWindow based progress note editor
     867          with a header line. The header either displays the episode
     868          this progress note is associated with or it allows for
     869          entering an episode name. The episode name either names
     870          an existing episode or is the name for a new episode.
     871
     872          This panel knows how to save it's data into the backend.
     873
     874          Can work as:
     875                  a) Progress note creation: displays an empty set of soap entries to
     876                  create a new soap note for the given episode (or unassociated)
     877          """
     878          #--------------------------------------------------------
     879 -        def __init__(self, parent, problem=None, input_defs=None):
     880                  """
     881                  Construct a new SOAP input widget.
     882
     883                  @param parent: the parent widget
     884
     885                  @param episode: the episode to create the SOAP editor for.
     886                  @type episode gmEMRStructItems.cEpisode instance or None (to create an
     887                  unassociated progress note). A gmEMRStructItems.cProblem instance is
     888                  also allowed to be passed, as the widget will obtain the related cEpisode.
     889
     890                  @param input_defs: the display and associated data for each displayed narrative
     891                  @type input_defs: a list of cSOAPLineDef instances
     892                  """
     893                  if not isinstance(problem, (gmEMRStructItems.cHealthIssue, gmEMRStructItems.cEpisode, gmEMRStructItems.cProblem, type(None))):
     894                          raise gmExceptions.ConstructorError('problem [%s] is of type %s, must be issue, episode, problem or None' % (str(problem), type(problem)))
     895
     896                  self.__is_saved = False
     897                  # do layout
     898                  wx.Panel.__init__(self, parent, -1, style = wx.NO_BORDER | wx.TAB_TRAVERSAL)
     899                  # - editor
     900                  if input_defs is None:
     901                          soap_lines = []
     902                          # make Richard the default ;-)
     903                          # FIXME: actually, should be read from backend
     904                          line = cSOAPLineDef()
     905                          line.label = _('Visit Purpose')
     906                          line.soap_cat = 's'
     907                          line.is_rfe = True
     908                          soap_lines.append(line)
     909
     910                          line = cSOAPLineDef()
     911                          line.label = _('History Taken')
     912                          line.soap_cat = 's'
     913                          soap_lines.append(line)
     914
     915                          line = cSOAPLineDef()
     916                          line.label = _('Findings')
     917                          line.soap_cat = 'o'
     918                          soap_lines.append(line)
     919
     920                          line = cSOAPLineDef()
     921                          line.label = _('Assessment')
     922                          line.soap_cat = 'a'
     923                          soap_lines.append(line)
     924
     925                          line = cSOAPLineDef()
     926                          line.label = _('Plan')
     927                          line.soap_cat = 'p'
     928                          soap_lines.append(line)
     929                  else:
     930                          soap_lines = input_defs
     931                  self.__soap_editor = cResizingSoapWin (
     932                          self,
     933                          size = wx.DefaultSize,
     934                          input_defs = soap_lines,
     935                          problem = problem
     936                  )
     937                  # - arrange
     938                  self.__szr_main = wx.BoxSizer(wx.VERTICAL)
     939                  self.__szr_main.Add(self.__soap_editor, 1, wx.EXPAND)
     940                  self.SetSizerAndFit(self.__szr_main)
     941          #--------------------------------------------------------
     942          # public API
     943          #--------------------------------------------------------
     944 -        def get_problem(self):
     945                  """Retrieve the related problem for this SOAP input widget.
     946                  """
     947                  return self.__soap_editor.get_problem()
     948          #--------------------------------------------------------
     949 -        def is_unassociated_editor(self):
     950                  """
     951                  Retrieves whether the current editor is not associated
     952                  with any episode.
     953                  """
     954                  return ((self.__problem is None) or (self.__problem['type'] == 'issue'))
     955          #--------------------------------------------------------
     956 -        def get_editor(self):
     957                  """Retrieves widget's SOAP text editor.
     958                  """
     959                  return self.__soap_editor
     960          #--------------------------------------------------------
     961 -        def Clear(self):
     962                  """Clear any entries in widget's SOAP text editor
     963                  """
     964                  self.__soap_editor.Clear()
     965          #--------------------------------------------------------
     966 -        def SetSaved(self, is_saved):
     967                  """
     968                  Set SOAP input widget saved (dumped to backend) state
     969
     970                  @param is_saved: Flag indicating wether the SOAP has been dumped to
     971                                                   persistent backend
     972                  @type is_saved: boolean
     973                  """
     974                  self.__is_saved = is_saved
     975                  self.Clear()
     976          #--------------------------------------------------------
     977 -        def IsSaved(self):
     978                  """
     979                  Check SOAP input widget saved (dumped to backend) state
     980                  """
     981                  return self.__is_saved
     982          #--------------------------------------------------------
     983 -        def save(self):
     984                  return self.__soap_editor.save()
     985          #--------------------------------------------------------
     986 -        def editor_empty(self):
     987                  return self.__soap_editor.is_empty()
     988  #============================================================
     989 -class cSingleBoxSOAP(wx.TextCtrl):
     990          """if we separate it out like this it can transparently gain features"""
     991 -        def __init__(self, *args, **kwargs):
     992                  wx.TextCtrl.__init__(self, *args, **kwargs)
     993  #============================================================
     994 -class cSingleBoxSOAPPanel(wx.Panel):
     995          """Single Box free text SOAP input.
     996
     997          This widget was suggested by David Guest on the mailing
     998          list. All it does is provide a single multi-line textbox
     999          for typing free-text clinical notes which are stored as
    1000          Subjective.
    1001          """
    1002 -        def __init__(self, *args, **kwargs):
    1003                  wx.Panel.__init__(self, *args, **kwargs)
    1004                  self.__do_layout()
    1005                  self.__pat = gmPerson.gmCurrentPatient()
    1006                  if not self.__register_events():
    1007                          raise gmExceptions.ConstructorError('cannot register interests')
    1008          #--------------------------------------------------------
    1009 -        def __do_layout(self):
    1010                  # large box for free-text clinical notes
    1011                  self.__soap_box = cSingleBoxSOAP (
    1012                          self,
    1013                          -1,
    1014                          '',
    1015                          style = wx.TE_MULTILINE
    1016                  )
    1017                  # buttons below that
    1018                  self.__BTN_save = wx.Button(self, wx.NewId(), _("save"))
    1019                  self.__BTN_save.SetToolTip(_('save clinical note in EMR'))
    1020                  self.__BTN_discard = wx.Button(self, wx.NewId(), _("discard"))
    1021                  self.__BTN_discard.SetToolTip(_('discard clinical note'))
    1022                  szr_btns = wx.BoxSizer(wx.HORIZONTAL)
    1023                  szr_btns.Add(self.__BTN_save, 1, wx.ALIGN_CENTER_HORIZONTAL, 0)
    1024                  szr_btns.Add(self.__BTN_discard, 1, wx.ALIGN_CENTER_HORIZONTAL, 0)
    1025                  # arrange widgets
    1026                  szr_outer = wx.StaticBoxSizer(wx.StaticBox(self, -1, _("clinical progress note")), wx.VERTICAL)
    1027                  szr_outer.Add(self.__soap_box, 1, wx.EXPAND, 0)
    1028                  szr_outer.Add(szr_btns, 0, wx.EXPAND, 0)
    1029                  # and do layout
    1030                  self.SetAutoLayout(1)
    1031                  self.SetSizer(szr_outer)
    1032                  szr_outer.Fit(self)
    1033                  szr_outer.SetSizeHints(self)
    1034                  self.Layout()
    1035          #--------------------------------------------------------
    1036 -        def __register_events(self):
    1037                  # wxPython events
    1038                  self.__BTN_save.Bind(wx.EVT_BUTTON, self._on_save_note)
    1039                  self.__BTN_discard.Bind(wx.EVT_BUTTON, self._on_discard_note)
    1040
    1041                  # client internal signals
    1042                  gmDispatcher.connect(signal = 'application_closing', receiver = self._save_note)
    1043                  # really should be synchronous:
    1044                  gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._save_note)
    1045
    1046                  return True
    1047          #--------------------------------------------------------
    1048          # event handlers
    1049          #--------------------------------------------------------
    1050 -        def _on_save_note(self, event):
    1051                  self.__save_note()
    1052                  #event.Skip()
    1053          #--------------------------------------------------------
    1054 -        def _on_discard_note(self, event):
    1055                  # FIXME: maybe ask for confirmation ?
    1056                  self.__soap_box.SetValue('')
    1057                  #event.Skip()
    1058          #--------------------------------------------------------
    1059          # internal helpers
    1060          #--------------------------------------------------------
    1061 -        def _save_note(self):
    1062                  # xxxxxx
    1063                  # FIXME: this should be a sync callback
    1064                  # xxxxxx
    1065                  wx.CallAfter(self.__save_note)
    1066          #--------------------------------------------------------
    1067 -        def __save_note(self):
    1068                  # sanity checks
    1069                  if self.__pat is None:
    1070                          return True
    1071                  if not self.__pat.connected:
    1072                          return True
    1073                  if not self.__soap_box.IsModified():
    1074                          return True
    1075                  note = self.__soap_box.GetValue()
    1076                  if note.strip() == '':
    1077                          return True
    1078                  # now save note
    1079                  emr = self.__pat.emr
    1080                  if emr is None:
    1081                          _log.error('cannot access clinical record of patient')
    1082                          return False
    1083                  if not emr.add_clin_narrative(note, soap_cat='s'):
    1084                          _log.error('error saving clinical note')
    1085                          return False
    1086                  self.__soap_box.SetValue('')
    1087                  return True
    1088  #============================================================
    1089  # main
    1090  #------------------------------------------------------------
    1091  if __name__ == "__main__":
    1092
    1093          import sys
    1094
    1095          from Gnumed.pycommon import gmPG2
    1096          #--------------------------------------------------------
    1097 -        def get_narrative(pk_encounter=None, pk_health_issue = None, default_labels=None):
    1098                  """
    1099                  Retrieve the soap editor input lines definitions built from
    1100                  all the narratives for the given issue along a specific
    1101                  encounter.
    1102
    1103                  @param pk_health_issue The id of the health issue to obtain the narratives for.
    1104                  @param pk_health_issue An integer instance
    1105
    1106                  @param pk_encounter The id of the encounter to obtain the narratives for.
    1107                  @type A gmEMRStructItems.cEncounter instance.
    1108
    1109                  @param default_labels: The user customized labels for each
    1110                  soap category.
    1111                  @type default_labels: A dictionary instance which keys are
    1112                  soap categories.
    1113                  """
    1114                  # custom labels
    1115                  if default_labels is None:
    1116                          default_labels = {
    1117                                  's': _('History Taken'),
    1118                                  'o': _('Findings'),
    1119                                  'a': _('Assessment'),
    1120                                  'p': _('Plan')
    1121                  }
    1122
    1123                  pat = gmPerson.gmCurrentPatient()
    1124                  emr = pat.emr
    1125                  soap_lines = []
    1126                  # for each soap cat
    1127                  for soap_cat in gmSOAPimporter.soap_bundle_SOAP_CATS:
    1128                          # retrieve narrative for given encounter
    1129                          narr_items =  emr.get_clin_narrative (
    1130                                  encounters = [pk_encounter],
    1131                                  issues = [pk_health_issue],
    1132                                  soap_cats = [soap_cat]
    1133                          )
    1134                          for narrative in narr_items:
    1135                                  try:
    1136                                          # FIXME: add more data such as doctor sig
    1137                                          label_txt = default_labels[narrative['soap_cat']]
    1138                                  except:
    1139                                          label_txt = narrative['soap_cat']
    1140                                  line = cSOAPLineDef()
    1141                                  line.label = label_txt
    1142                                  line.text = narrative['narrative']
    1143  #                               line.data['narrative instance'] = narrative
    1144                                  soap_lines.append(line)
    1145                  return soap_lines
    1146          #--------------------------------------------------------
    1147 -        def create_widget_on_test_kwd1(*args, **kwargs):
    1148                  print("test keyword must have been typed...")
    1149                  print("actually this would have to return a suitable wx.Window subclass instance")
    1150                  print("args:", args)
    1151                  print("kwd args:")
    1152                  for key in kwargs.keys():
    1153                          print(key, "->", kwargs[key])
    1154          #--------------------------------------------------------
    1155 -        def create_widget_on_test_kwd2(*args, **kwargs):
    1156                  msg = (
    1157                          "test keyword must have been typed...\n"
    1158                          "actually this would have to return a suitable wx.Window subclass instance\n"
    1159                  )
    1160                  for arg in args:
    1161                          msg = msg + "\narg ==> %s" % arg
    1162                  for key in kwargs.keys():
    1163                          msg = msg + "\n%s ==> %s" % (key, kwargs[key])
    1164                  gmGuiHelpers.gm_show_info (
    1165                          aMessage = msg,
    1166                          aTitle = 'msg box on create_widget from test_keyword'
    1167                  )
    1168          #--------------------------------------------------------
    1169 -        def test_soap_notebook():
    1170                  print('testing notebooked soap input...')
    1171                  application = wx.PyWidgetTester(size=(800,500))
    1172                  soap_input = cProgressNoteInputNotebook(application.frame, -1)
    1173                  application.frame.Show(True)
    1174                  application.MainLoop()
    1175          #--------------------------------------------------------
    1176 -        def test_soap_notebook_panel():
    1177                  print('testing notebooked soap panel...')
    1178                  application = wx.PyWidgetTester(size=(800,500))
    1179                  soap_input = cNotebookedProgressNoteInputPanel(application.frame, -1)
    1180                  application.frame.Show(True)
    1181                  application.MainLoop()
    1182          #--------------------------------------------------------
    1183
    1184          try:
    1185                  # obtain patient
    1186                  patient = gmPersonSearch.ask_for_patient()
    1187                  if patient is None:
    1188                          print("No patient. Exiting gracefully...")
    1189                          sys.exit(0)
    1190                  gmPatSearchWidgets.set_active_patient(patient=patient)
    1191
    1192                  #test_soap_notebook()
    1193                  test_soap_notebook_panel()
    1194
    1195  #               # multisash soap
    1196  #               print 'testing multisashed soap input...'
    1197  #               application = wx.PyWidgetTester(size=(800,500))
    1198  #               soap_input = cMultiSashedProgressNoteInputPanel(application.frame, -1)
    1199  #               application.frame.Show(True)
    1200  #               application.MainLoop()
    1201
    1202  #               # soap widget displaying all narratives for an issue along an encounter
    1203  #               print 'testing soap editor for encounter narratives...'
    1204  #               episode = gmEMRStructItems.cEpisode(aPK_obj=1)
    1205  #               encounter = gmEMRStructItems.cEncounter(aPK_obj=1)
    1206  #               narrative = get_narrative(pk_encounter = encounter['pk_encounter'], pk_health_issue = episode['pk_health_issue'])
    1207  #               default_labels = {'s':'Subjective', 'o':'Objective', 'a':'Assesment', 'p':'Plan'}
    1208  #               app = wx.PyWidgetTester(size=(300,500))
    1209  #               app.SetWidget(cResizingSoapPanel, episode, narrative)
    1210  #               app.MainLoop()
    1211  #               del app
    1212
    1213  #               # soap progress note for episode
    1214  #               print 'testing soap editor for episode...'
    1215  #               app = wx.PyWidgetTester(size=(300,300))
    1216  #               app.SetWidget(cResizingSoapPanel, episode)
    1217  #               app.MainLoop()
    1218  #               del app
    1219
    1220  #               # soap progress note for problem
    1221  #               print 'testing soap editor for problem...'
    1222  #               problem = gmEMRStructItems.cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': 1})
    1223  #               app = wx.PyWidgetTester(size=(300,300))
    1224  #               app.SetWidget(cResizingSoapPanel, problem)
    1225  #               app.MainLoop()
    1226  #               del app
    1227
    1228  #               # unassociated soap progress note
    1229  #               print 'testing unassociated soap editor...'
    1230  #               app = wx.PyWidgetTester(size=(300,300))
    1231  #               app.SetWidget(cResizingSoapPanel, None)
    1232  #               app.MainLoop()
    1233  #               del app
    1234
    1235  #               # unstructured progress note
    1236  #               print 'testing unstructured progress note...'
    1237  #               app = wx.PyWidgetTester(size=(600,600))
    1238  #               app.SetWidget(cSingleBoxSOAPPanel, -1)
    1239  #               app.MainLoop()
    1240
    1241          except Exception:
    1242                  _log.exception("unhandled exception caught !")
    1243                  # but re-raise them
    1244                  raise
    1245
    1246  #============================================================
    1247