Gnumed.wxpython.gmDocumentWidgets

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

# Source Code for Module Gnumed.wxpython.gmDocumentWidgets

       1  """GNUmed medical document handling widgets.
       2  """
       3  #================================================================
       4  __version__ = "$Revision: 1.187 $"
       5  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
       6
       7  import os.path
       8  import os
       9  import sys
      10  import re as regex
      11  import logging
      12
      13
      14  import wx
      15
      16
      17  if __name__ == '__main__':
      18          sys.path.insert(0, '../../')
      19  from Gnumed.pycommon import gmI18N
      20  from Gnumed.pycommon import gmCfg
      21  from Gnumed.pycommon import gmPG2
      22  from Gnumed.pycommon import gmMimeLib
      23  from Gnumed.pycommon import gmMatchProvider
      24  from Gnumed.pycommon import gmDispatcher
      25  from Gnumed.pycommon import gmDateTime
      26  from Gnumed.pycommon import gmTools
      27  from Gnumed.pycommon import gmShellAPI
      28  from Gnumed.pycommon import gmHooks
      29
      30  from Gnumed.business import gmPerson
      31  from Gnumed.business import gmStaff
      32  from Gnumed.business import gmDocuments
      33  from Gnumed.business import gmEMRStructItems
      34  from Gnumed.business import gmPraxis
      35
      36  from Gnumed.wxpython import gmGuiHelpers
      37  from Gnumed.wxpython import gmRegetMixin
      38  from Gnumed.wxpython import gmPhraseWheel
      39  from Gnumed.wxpython import gmPlugin
      40  from Gnumed.wxpython import gmEMRStructWidgets
      41  from Gnumed.wxpython import gmEncounterWidgets
      42  from Gnumed.wxpython import gmListWidgets
      43
      44
      45  _log = logging.getLogger('gm.ui')
      46  _log.info(__version__)
      47
      48
      49  default_chunksize = 1 * 1024 * 1024             # 1 MB
      50  #============================================================
      51 -def manage_document_descriptions(parent=None, document=None):
      52
      53          #-----------------------------------
      54          def delete_item(item):
      55                  doit = gmGuiHelpers.gm_show_question (
      56                          _(      'Are you sure you want to delete this\n'
      57                                  'description from the document ?\n'
      58                          ),
      59                          _('Deleting document description')
      60                  )
      61                  if not doit:
      62                          return True
      63
      64                  document.delete_description(pk = item[0])
      65                  return True
      66          #-----------------------------------
      67          def add_item():
      68                  dlg = gmGuiHelpers.cMultilineTextEntryDlg (
      69                          parent,
      70                          -1,
      71                          title = _('Adding document description'),
      72                          msg = _('Below you can add a document description.\n')
      73                  )
      74                  result = dlg.ShowModal()
      75                  if result == wx.ID_SAVE:
      76                          document.add_description(dlg.value)
      77
      78                  dlg.Destroy()
      79                  return True
      80          #-----------------------------------
      81          def edit_item(item):
      82                  dlg = gmGuiHelpers.cMultilineTextEntryDlg (
      83                          parent,
      84                          -1,
      85                          title = _('Editing document description'),
      86                          msg = _('Below you can edit the document description.\n'),
      87                          text = item[1]
      88                  )
      89                  result = dlg.ShowModal()
      90                  if result == wx.ID_SAVE:
      91                          document.update_description(pk = item[0], description = dlg.value)
      92
      93                  dlg.Destroy()
      94                  return True
      95          #-----------------------------------
      96          def refresh_list(lctrl):
      97                  descriptions = document.get_descriptions()
      98
      99                  lctrl.set_string_items(items = [
     100                          u'%s%s' % ( (u' '.join(regex.split('\r\n+|\r+|\n+|\t+', desc[1])))[:30], gmTools.u_ellipsis )
     101                          for desc in descriptions
     102                  ])
     103                  lctrl.set_data(data = descriptions)
     104          #-----------------------------------
     105
     106          gmListWidgets.get_choices_from_list (
     107                  parent = parent,
     108                  msg = _('Select the description you are interested in.\n'),
     109                  caption = _('Managing document descriptions'),
     110                  columns = [_('Description')],
     111                  edit_callback = edit_item,
     112                  new_callback = add_item,
     113                  delete_callback = delete_item,
     114                  refresh_callback = refresh_list,
     115                  single_selection = True,
     116                  can_return_empty = True
     117          )
     118
     119          return True
     120  #============================================================
     121 -def _save_file_as_new_document(**kwargs):
     122          try:
     123                  del kwargs['signal']
     124                  del kwargs['sender']
     125          except KeyError:
     126                  pass
     127          wx.CallAfter(save_file_as_new_document, **kwargs)
     128
     129 -def _save_files_as_new_document(**kwargs):
     130          try:
     131                  del kwargs['signal']
     132                  del kwargs['sender']
     133          except KeyError:
     134                  pass
     135          wx.CallAfter(save_files_as_new_document, **kwargs)
     136  #----------------------
     137 -def save_file_as_new_document(parent=None, filename=None, document_type=None, unlock_patient=False, episode=None, review_as_normal=False):
     138          return save_files_as_new_document (
     139                  parent = parent,
     140                  filenames = [filename],
     141                  document_type = document_type,
     142                  unlock_patient = unlock_patient,
     143                  episode = episode,
     144                  review_as_normal = review_as_normal
     145          )
     146  #----------------------
     147 -def save_files_as_new_document(parent=None, filenames=None, document_type=None, unlock_patient=False, episode=None, review_as_normal=False, reference=None):
     148
     149          pat = gmPerson.gmCurrentPatient()
     150          if not pat.connected:
     151                  return None
     152
     153          emr = pat.get_emr()
     154
     155          if parent is None:
     156                  parent = wx.GetApp().GetTopWindow()
     157
     158          if episode is None:
     159                  all_epis = emr.get_episodes()
     160                  # FIXME: what to do here ? probably create dummy episode
     161                  if len(all_epis) == 0:
     162                          episode = emr.add_episode(episode_name = _('Documents'), is_open = False)
     163                  else:
     164                          dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg(parent = parent, id = -1, episodes = all_epis)
     165                          dlg.SetTitle(_('Select the episode under which to file the document ...'))
     166                          btn_pressed = dlg.ShowModal()
     167                          episode = dlg.get_selected_item_data(only_one = True)
     168                          dlg.Destroy()
     169
     170                          if (btn_pressed == wx.ID_CANCEL) or (episode is None):
     171                                  if unlock_patient:
     172                                          pat.locked = False
     173                                  return None
     174
     175          doc_type = gmDocuments.create_document_type(document_type = document_type)
     176
     177          docs_folder = pat.get_document_folder()
     178          doc = docs_folder.add_document (
     179                  document_type = doc_type['pk_doc_type'],
     180                  encounter = emr.active_encounter['pk_encounter'],
     181                  episode = episode['pk_episode']
     182          )
     183          if reference is not None:
     184                  doc['ext_ref'] = reference
     185                  doc.save()
     186          doc.add_parts_from_files(files = filenames)
     187
     188          if review_as_normal:
     189                  doc.set_reviewed(technically_abnormal = False, clinically_relevant = False)
     190
     191          if unlock_patient:
     192                  pat.locked = False
     193
     194          gmDispatcher.send(signal = 'statustext', msg = _('Imported new document from %s.') % filenames, beep = True)
     195
     196          return doc
     197  #----------------------
     198  gmDispatcher.connect(signal = u'import_document_from_file', receiver = _save_file_as_new_document)
     199  gmDispatcher.connect(signal = u'import_document_from_files', receiver = _save_files_as_new_document)
     200  #============================================================
     201 -class cDocumentCommentPhraseWheel(gmPhraseWheel.cPhraseWheel):
     202          """Let user select a document comment from all existing comments."""
     203 -        def __init__(self, *args, **kwargs):
     204
     205                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     206
     207                  context = {
     208                          u'ctxt_doc_type': {
     209                                  u'where_part': u'and fk_type = %(pk_doc_type)s',
     210                                  u'placeholder': u'pk_doc_type'
     211                          }
     212                  }
     213
     214                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     215                          queries = [u"""
     216  SELECT
     217          data,
     218          field_label,
     219          list_label
     220  FROM (
     221          SELECT DISTINCT ON (field_label) *
     222          FROM (
     223                  -- constrained by doc type
     224                  SELECT
     225                          comment AS data,
     226                          comment AS field_label,
     227                          comment AS list_label,
     228                          1 AS rank
     229                  FROM blobs.doc_med
     230                  WHERE
     231                          comment %(fragment_condition)s
     232                          %(ctxt_doc_type)s
     233
     234                  UNION ALL
     235
     236                  SELECT
     237                          comment AS data,
     238                          comment AS field_label,
     239                          comment AS list_label,
     240                          2 AS rank
     241                  FROM blobs.doc_med
     242                  WHERE
     243                          comment %(fragment_condition)s
     244          ) AS q_union
     245  ) AS q_distinct
     246  ORDER BY rank, list_label
     247  LIMIT 25"""],
     248                          context = context
     249                  )
     250                  mp.setThresholds(3, 5, 7)
     251                  mp.unset_context(u'pk_doc_type')
     252
     253                  self.matcher = mp
     254                  self.picklist_delay = 50
     255
     256                  self.SetToolTipString(_('Enter a comment on the document.'))
     257  #============================================================
     258  # document type widgets
     259  #============================================================
     260 -def manage_document_types(parent=None):
     261
     262          if parent is None:
     263                  parent = wx.GetApp().GetTopWindow()
     264
     265          dlg = cEditDocumentTypesDlg(parent = parent)
     266          dlg.ShowModal()
     267  #============================================================
     268  from Gnumed.wxGladeWidgets import wxgEditDocumentTypesDlg
     269
     270 -class cEditDocumentTypesDlg(wxgEditDocumentTypesDlg.wxgEditDocumentTypesDlg):
     271          """A dialog showing a cEditDocumentTypesPnl."""
     272
     273 -        def __init__(self, *args, **kwargs):
     274                  wxgEditDocumentTypesDlg.wxgEditDocumentTypesDlg.__init__(self, *args, **kwargs)
     275
     276  #============================================================
     277  from Gnumed.wxGladeWidgets import wxgEditDocumentTypesPnl
     278
     279 -class cEditDocumentTypesPnl(wxgEditDocumentTypesPnl.wxgEditDocumentTypesPnl):
     280          """A panel grouping together fields to edit the list of document types."""
     281
     282 -        def __init__(self, *args, **kwargs):
     283                  wxgEditDocumentTypesPnl.wxgEditDocumentTypesPnl.__init__(self, *args, **kwargs)
     284                  self.__init_ui()
     285                  self.__register_interests()
     286                  self.repopulate_ui()
     287          #--------------------------------------------------------
     288 -        def __init_ui(self):
     289                  self._LCTRL_doc_type.set_columns([_('Type'), _('Translation'), _('User defined'), _('In use')])
     290                  self._LCTRL_doc_type.set_column_widths()
     291          #--------------------------------------------------------
     292 -        def __register_interests(self):
     293                  gmDispatcher.connect(signal = u'blobs.doc_type_mod_db', receiver = self._on_doc_type_mod_db)
     294          #--------------------------------------------------------
     295 -        def _on_doc_type_mod_db(self):
     296                  wx.CallAfter(self.repopulate_ui)
     297          #--------------------------------------------------------
     298 -        def repopulate_ui(self):
     299
     300                  self._LCTRL_doc_type.DeleteAllItems()
     301
     302                  doc_types = gmDocuments.get_document_types()
     303                  pos = len(doc_types) + 1
     304
     305                  for doc_type in doc_types:
     306                          row_num = self._LCTRL_doc_type.InsertStringItem(pos, label = doc_type['type'])
     307                          self._LCTRL_doc_type.SetStringItem(index = row_num, col = 1, label = doc_type['l10n_type'])
     308                          if doc_type['is_user_defined']:
     309                                  self._LCTRL_doc_type.SetStringItem(index = row_num, col = 2, label = ' X ')
     310                          if doc_type['is_in_use']:
     311                                  self._LCTRL_doc_type.SetStringItem(index = row_num, col = 3, label = ' X ')
     312
     313                  if len(doc_types) > 0:
     314                          self._LCTRL_doc_type.set_data(data = doc_types)
     315                          self._LCTRL_doc_type.SetColumnWidth(col=0, width=wx.LIST_AUTOSIZE)
     316                          self._LCTRL_doc_type.SetColumnWidth(col=1, width=wx.LIST_AUTOSIZE)
     317                          self._LCTRL_doc_type.SetColumnWidth(col=2, width=wx.LIST_AUTOSIZE_USEHEADER)
     318                          self._LCTRL_doc_type.SetColumnWidth(col=3, width=wx.LIST_AUTOSIZE_USEHEADER)
     319
     320                  self._TCTRL_type.SetValue('')
     321                  self._TCTRL_l10n_type.SetValue('')
     322
     323                  self._BTN_set_translation.Enable(False)
     324                  self._BTN_delete.Enable(False)
     325                  self._BTN_add.Enable(False)
     326                  self._BTN_reassign.Enable(False)
     327
     328                  self._LCTRL_doc_type.SetFocus()
     329          #--------------------------------------------------------
     330          # event handlers
     331          #--------------------------------------------------------
     332 -        def _on_list_item_selected(self, evt):
     333                  doc_type = self._LCTRL_doc_type.get_selected_item_data()
     334
     335                  self._TCTRL_type.SetValue(doc_type['type'])
     336                  self._TCTRL_l10n_type.SetValue(doc_type['l10n_type'])
     337
     338                  self._BTN_set_translation.Enable(True)
     339                  self._BTN_delete.Enable(not bool(doc_type['is_in_use']))
     340                  self._BTN_add.Enable(False)
     341                  self._BTN_reassign.Enable(True)
     342
     343                  return
     344          #--------------------------------------------------------
     345 -        def _on_type_modified(self, event):
     346                  self._BTN_set_translation.Enable(False)
     347                  self._BTN_delete.Enable(False)
     348                  self._BTN_reassign.Enable(False)
     349
     350                  self._BTN_add.Enable(True)
     351  #               self._LCTRL_doc_type.deselect_selected_item()
     352                  return
     353          #--------------------------------------------------------
     354 -        def _on_set_translation_button_pressed(self, event):
     355                  doc_type = self._LCTRL_doc_type.get_selected_item_data()
     356                  if doc_type.set_translation(translation = self._TCTRL_l10n_type.GetValue().strip()):
     357                          wx.CallAfter(self.repopulate_ui)
     358
     359                  return
     360          #--------------------------------------------------------
     361 -        def _on_delete_button_pressed(self, event):
     362                  doc_type = self._LCTRL_doc_type.get_selected_item_data()
     363                  if doc_type['is_in_use']:
     364                          gmGuiHelpers.gm_show_info (
     365                                  _(
     366                                          'Cannot delete document type\n'
     367                                          ' [%s]\n'
     368                                          'because it is currently in use.'
     369                                  ) % doc_type['l10n_type'],
     370                                  _('deleting document type')
     371                          )
     372                          return
     373
     374                  gmDocuments.delete_document_type(document_type = doc_type)
     375
     376                  return
     377          #--------------------------------------------------------
     378 -        def _on_add_button_pressed(self, event):
     379                  desc = self._TCTRL_type.GetValue().strip()
     380                  if desc != '':
     381                          doc_type = gmDocuments.create_document_type(document_type = desc)               # does not create dupes
     382                          l10n_desc = self._TCTRL_l10n_type.GetValue().strip()
     383                          if (l10n_desc != '') and (l10n_desc != doc_type['l10n_type']):
     384                                  doc_type.set_translation(translation = l10n_desc)
     385
     386                  return
     387          #--------------------------------------------------------
     388 -        def _on_reassign_button_pressed(self, event):
     389
     390                  orig_type = self._LCTRL_doc_type.get_selected_item_data()
     391                  doc_types = gmDocuments.get_document_types()
     392
     393                  new_type = gmListWidgets.get_choices_from_list (
     394                          parent = self,
     395                          msg = _(
     396                                  'From the list below select the document type you want\n'
     397                                  'all documents currently classified as:\n\n'
     398                                  ' "%s"\n\n'
     399                                  'to be changed to.\n\n'
     400                                  'Be aware that this change will be applied to ALL such documents. If there\n'
     401                                  'are many documents to change it can take quite a while.\n\n'
     402                                  'Make sure this is what you want to happen !\n'
     403                          ) % orig_type['l10n_type'],
     404                          caption = _('Reassigning document type'),
     405                          choices = [ [gmTools.bool2subst(dt['is_user_defined'], u'X', u''), dt['type'], dt['l10n_type']] for dt in doc_types ],
     406                          columns = [_('User defined'), _('Type'), _('Translation')],
     407                          data = doc_types,
     408                          single_selection = True
     409                  )
     410
     411                  if new_type is None:
     412                          return
     413
     414                  wx.BeginBusyCursor()
     415                  gmDocuments.reclassify_documents_by_type(original_type = orig_type, target_type = new_type)
     416                  wx.EndBusyCursor()
     417
     418                  return
     419  #============================================================
     420 -class cDocumentTypeSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
     421          """Let user select a document type."""
     422 -        def __init__(self, *args, **kwargs):
     423
     424                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     425
     426                  mp = gmMatchProvider.cMatchProvider_SQL2 (
     427                          queries = [
     428  u"""SELECT
     429          data,
     430          field_label,
     431          list_label
     432  FROM ((
     433          SELECT
     434                  pk_doc_type AS data,
     435                  l10n_type AS field_label,
     436                  l10n_type AS list_label,
     437                  1 AS rank
     438          FROM blobs.v_doc_type
     439          WHERE
     440                  is_user_defined IS True
     441                          AND
     442                  l10n_type %(fragment_condition)s
     443  ) UNION (
     444          SELECT
     445                  pk_doc_type AS data,
     446                  l10n_type AS field_label,
     447                  l10n_type AS list_label,
     448                  2 AS rank
     449          FROM blobs.v_doc_type
     450          WHERE
     451                  is_user_defined IS False
     452                          AND
     453                  l10n_type %(fragment_condition)s
     454  )) AS q1
     455  ORDER BY q1.rank, q1.list_label"""]
     456                          )
     457                  mp.setThresholds(2, 4, 6)
     458
     459                  self.matcher = mp
     460                  self.picklist_delay = 50
     461
     462                  self.SetToolTipString(_('Select the document type.'))
     463          #--------------------------------------------------------
     464 -        def _create_data(self):
     465
     466                  doc_type = self.GetValue().strip()
     467                  if doc_type == u'':
     468                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot create document type without name.'), beep = True)
     469                          _log.debug('cannot create document type without name')
     470                          return
     471
     472                  pk = gmDocuments.create_document_type(doc_type)['pk_doc_type']
     473                  if pk is None:
     474                          self.data = {}
     475                  else:
     476                          self.SetText (
     477                                  value = doc_type,
     478                                  data = pk
     479                          )
     480  #============================================================
     481  # document review widgets
     482  #============================================================
     483 -def review_document_part(parent=None, part=None):
     484          if parent is None:
     485                  parent = wx.GetApp().GetTopWindow()
     486          dlg = cReviewDocPartDlg (
     487                  parent = parent,
     488                  id = -1,
     489                  part = part
     490          )
     491          dlg.ShowModal()
     492          dlg.Destroy()
     493  #------------------------------------------------------------
     494 -def review_document(parent=None, document=None):
     495          return review_document_part(parent = parent, part = document)
     496  #------------------------------------------------------------
     497  from Gnumed.wxGladeWidgets import wxgReviewDocPartDlg
     498
     499 -class cReviewDocPartDlg(wxgReviewDocPartDlg.wxgReviewDocPartDlg):
     500 -        def __init__(self, *args, **kwds):
     501                  """Support parts and docs now.
     502                  """
     503                  part = kwds['part']
     504                  del kwds['part']
     505                  wxgReviewDocPartDlg.wxgReviewDocPartDlg.__init__(self, *args, **kwds)
     506
     507                  if isinstance(part, gmDocuments.cDocumentPart):
     508                          self.__part = part
     509                          self.__doc = self.__part.get_containing_document()
     510                          self.__reviewing_doc = False
     511                  elif isinstance(part, gmDocuments.cDocument):
     512                          self.__doc = part
     513                          if len(self.__doc.parts) == 0:
     514                                  self.__part = None
     515                          else:
     516                                  self.__part = self.__doc.parts[0]
     517                          self.__reviewing_doc = True
     518                  else:
     519                          raise ValueError('<part> must be gmDocuments.cDocument or gmDocuments.cDocumentPart instance, got <%s>' % type(part))
     520
     521                  self.__init_ui_data()
     522          #--------------------------------------------------------
     523          # internal API
     524          #--------------------------------------------------------
     525 -        def __init_ui_data(self):
     526                  # FIXME: fix this
     527                  # associated episode (add " " to avoid popping up pick list)
     528                  self._PhWheel_episode.SetText('%s ' % self.__doc['episode'], self.__doc['pk_episode'])
     529                  self._PhWheel_doc_type.SetText(value = self.__doc['l10n_type'], data = self.__doc['pk_type'])
     530                  self._PhWheel_doc_type.add_callback_on_set_focus(self._on_doc_type_gets_focus)
     531                  self._PhWheel_doc_type.add_callback_on_lose_focus(self._on_doc_type_loses_focus)
     532
     533                  if self.__reviewing_doc:
     534                          self._PRW_doc_comment.SetText(gmTools.coalesce(self.__doc['comment'], ''))
     535                          self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = self.__doc['pk_type'])
     536                  else:
     537                          self._PRW_doc_comment.SetText(gmTools.coalesce(self.__part['obj_comment'], ''))
     538
     539                  fts = gmDateTime.cFuzzyTimestamp(timestamp = self.__doc['clin_when'])
     540                  self._PhWheel_doc_date.SetText(fts.strftime('%Y-%m-%d'), fts)
     541                  self._TCTRL_reference.SetValue(gmTools.coalesce(self.__doc['ext_ref'], ''))
     542                  if self.__reviewing_doc:
     543                          self._TCTRL_filename.Enable(False)
     544                          self._SPINCTRL_seq_idx.Enable(False)
     545                  else:
     546                          self._TCTRL_filename.SetValue(gmTools.coalesce(self.__part['filename'], ''))
     547                          self._SPINCTRL_seq_idx.SetValue(gmTools.coalesce(self.__part['seq_idx'], 0))
     548
     549                  self._LCTRL_existing_reviews.InsertColumn(0, _('who'))
     550                  self._LCTRL_existing_reviews.InsertColumn(1, _('when'))
     551                  self._LCTRL_existing_reviews.InsertColumn(2, _('+/-'))
     552                  self._LCTRL_existing_reviews.InsertColumn(3, _('!'))
     553                  self._LCTRL_existing_reviews.InsertColumn(4, _('comment'))
     554
     555                  self.__reload_existing_reviews()
     556
     557                  if self._LCTRL_existing_reviews.GetItemCount() > 0:
     558                          self._LCTRL_existing_reviews.SetColumnWidth(col=0, width=wx.LIST_AUTOSIZE)
     559                          self._LCTRL_existing_reviews.SetColumnWidth(col=1, width=wx.LIST_AUTOSIZE)
     560                          self._LCTRL_existing_reviews.SetColumnWidth(col=2, width=wx.LIST_AUTOSIZE_USEHEADER)
     561                          self._LCTRL_existing_reviews.SetColumnWidth(col=3, width=wx.LIST_AUTOSIZE_USEHEADER)
     562                          self._LCTRL_existing_reviews.SetColumnWidth(col=4, width=wx.LIST_AUTOSIZE)
     563
     564                  if self.__part is None:
     565                          self._ChBOX_review.SetValue(False)
     566                          self._ChBOX_review.Enable(False)
     567                          self._ChBOX_abnormal.Enable(False)
     568                          self._ChBOX_relevant.Enable(False)
     569                          self._ChBOX_sign_all_pages.Enable(False)
     570                  else:
     571                          me = gmStaff.gmCurrentProvider()
     572                          if self.__part['pk_intended_reviewer'] == me['pk_staff']:
     573                                  msg = _('(you are the primary reviewer)')
     574                          else:
     575                                  other = gmStaff.cStaff(aPK_obj = self.__part['pk_intended_reviewer'])
     576                                  msg = _('(someone else is the intended reviewer: %s)') % other['short_alias']
     577                          self._TCTRL_responsible.SetValue(msg)
     578                          # init my review if any
     579                          if self.__part['reviewed_by_you']:
     580                                  revs = self.__part.get_reviews()
     581                                  for rev in revs:
     582                                          if rev['is_your_review']:
     583                                                  self._ChBOX_abnormal.SetValue(bool(rev[2]))
     584                                                  self._ChBOX_relevant.SetValue(bool(rev[3]))
     585                                                  break
     586
     587                          self._ChBOX_sign_all_pages.SetValue(self.__reviewing_doc)
     588
     589                  return True
     590          #--------------------------------------------------------
     591 -        def __reload_existing_reviews(self):
     592                  self._LCTRL_existing_reviews.DeleteAllItems()
     593                  if self.__part is None:
     594                          return True
     595                  revs = self.__part.get_reviews()                # FIXME: this is ugly as sin, it should be dicts, not lists
     596                  if len(revs) == 0:
     597                          return True
     598                  # find special reviews
     599                  review_by_responsible_doc = None
     600                  reviews_by_others = []
     601                  for rev in revs:
     602                          if rev['is_review_by_responsible_reviewer'] and not rev['is_your_review']:
     603                                  review_by_responsible_doc = rev
     604                          if not (rev['is_review_by_responsible_reviewer'] or rev['is_your_review']):
     605                                  reviews_by_others.append(rev)
     606                  # display them
     607                  if review_by_responsible_doc is not None:
     608                          row_num = self._LCTRL_existing_reviews.InsertStringItem(sys.maxint, label=review_by_responsible_doc[0])
     609                          self._LCTRL_existing_reviews.SetItemTextColour(row_num, col=wx.BLUE)
     610                          self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=0, label=review_by_responsible_doc[0])
     611                          self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=1, label=review_by_responsible_doc[1].strftime('%x %H:%M'))
     612                          if review_by_responsible_doc['is_technically_abnormal']:
     613                                  self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=2, label=u'X')
     614                          if review_by_responsible_doc['clinically_relevant']:
     615                                  self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=3, label=u'X')
     616                          self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=4, label=review_by_responsible_doc[6])
     617                          row_num += 1
     618                  for rev in reviews_by_others:
     619                          row_num = self._LCTRL_existing_reviews.InsertStringItem(sys.maxint, label=rev[0])
     620                          self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=0, label=rev[0])
     621                          self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=1, label=rev[1].strftime('%x %H:%M'))
     622                          if rev['is_technically_abnormal']:
     623                                  self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=2, label=u'X')
     624                          if rev['clinically_relevant']:
     625                                  self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=3, label=u'X')
     626                          self._LCTRL_existing_reviews.SetStringItem(index = row_num, col=4, label=rev[6])
     627                  return True
     628          #--------------------------------------------------------
     629          # event handlers
     630          #--------------------------------------------------------
     631 -        def _on_save_button_pressed(self, evt):
     632                  """Save the metadata to the backend."""
     633
     634                  evt.Skip()
     635
     636                  # 1) handle associated episode
     637                  pk_episode = self._PhWheel_episode.GetData(can_create=True, is_open=True)
     638                  if pk_episode is None:
     639                          gmGuiHelpers.gm_show_error (
     640                                  _('Cannot create episode\n [%s]'),
     641                                  _('Editing document properties')
     642                          )
     643                          return False
     644
     645                  doc_type = self._PhWheel_doc_type.GetData(can_create = True)
     646                  if doc_type is None:
     647                          gmDispatcher.send(signal='statustext', msg=_('Cannot change document type to [%s].') % self._PhWheel_doc_type.GetValue().strip())
     648                          return False
     649
     650                  # since the phrasewheel operates on the active
     651                  # patient all episodes really should belong
     652                  # to it so we don't check patient change
     653                  self.__doc['pk_episode'] = pk_episode
     654                  self.__doc['pk_type'] = doc_type
     655                  if self.__reviewing_doc:
     656                          self.__doc['comment'] = self._PRW_doc_comment.GetValue().strip()
     657                  # FIXME: a rather crude way of error checking:
     658                  if self._PhWheel_doc_date.GetData() is not None:
     659                          self.__doc['clin_when'] = self._PhWheel_doc_date.GetData().get_pydt()
     660                  self.__doc['ext_ref'] = self._TCTRL_reference.GetValue().strip()
     661
     662                  success, data = self.__doc.save_payload()
     663                  if not success:
     664                          gmGuiHelpers.gm_show_error (
     665                                  _('Cannot link the document to episode\n\n [%s]') % epi_name,
     666                                  _('Editing document properties')
     667                          )
     668                          return False
     669
     670                  # 2) handle review
     671                  if self._ChBOX_review.GetValue():
     672                          provider = gmStaff.gmCurrentProvider()
     673                          abnormal = self._ChBOX_abnormal.GetValue()
     674                          relevant = self._ChBOX_relevant.GetValue()
     675                          msg = None
     676                          if self.__reviewing_doc:                # - on all pages
     677                                  if not self.__doc.set_reviewed(technically_abnormal = abnormal, clinically_relevant = relevant):
     678                                          msg = _('Error setting "reviewed" status of this document.')
     679                                  if self._ChBOX_responsible.GetValue():
     680                                          if not self.__doc.set_primary_reviewer(reviewer = provider['pk_staff']):
     681                                                  msg = _('Error setting responsible clinician for this document.')
     682                          else:                                                           # - just on this page
     683                                  if not self.__part.set_reviewed(technically_abnormal = abnormal, clinically_relevant = relevant):
     684                                          msg = _('Error setting "reviewed" status of this part.')
     685                                  if self._ChBOX_responsible.GetValue():
     686                                          self.__part['pk_intended_reviewer'] = provider['pk_staff']
     687                          if msg is not None:
     688                                  gmGuiHelpers.gm_show_error(msg, _('Editing document properties'))
     689                                  return False
     690
     691                  # 3) handle "page" specific parts
     692                  if not self.__reviewing_doc:
     693                          self.__part['filename'] = gmTools.none_if(self._TCTRL_filename.GetValue().strip(), u'')
     694                          new_idx = gmTools.none_if(self._SPINCTRL_seq_idx.GetValue(), 0)
     695                          if self.__part['seq_idx'] != new_idx:
     696                                  if new_idx in self.__doc['seq_idx_list']:
     697                                          msg = _(
     698                                                  'Cannot set page number to [%s] because\n'
     699                                                  'another page with this number exists.\n'
     700                                                  '\n'
     701                                                  'Page numbers in use:\n'
     702                                                  '\n'
     703                                                  ' %s'
     704                                          ) % (
     705                                                  new_idx,
     706                                                  self.__doc['seq_idx_list']
     707                                          )
     708                                          gmGuiHelpers.gm_show_error(msg, _('Editing document part properties'))
     709                                  else:
     710                                          self.__part['seq_idx'] = new_idx
     711                          self.__part['obj_comment'] = self._PRW_doc_comment.GetValue().strip()
     712                          success, data = self.__part.save_payload()
     713                          if not success:
     714                                  gmGuiHelpers.gm_show_error (
     715                                          _('Error saving part properties.'),
     716                                          _('Editing document part properties')
     717                                  )
     718                                  return False
     719
     720                  return True
     721          #--------------------------------------------------------
     722 -        def _on_reviewed_box_checked(self, evt):
     723                  state = self._ChBOX_review.GetValue()
     724                  self._ChBOX_abnormal.Enable(enable = state)
     725                  self._ChBOX_relevant.Enable(enable = state)
     726                  self._ChBOX_responsible.Enable(enable = state)
     727          #--------------------------------------------------------
     728 -        def _on_doc_type_gets_focus(self):
     729                  """Per Jim: Changing the doc type happens a lot more often
     730                     then correcting spelling, hence select-all on getting focus.
     731                  """
     732                  self._PhWheel_doc_type.SetSelection(-1, -1)
     733          #--------------------------------------------------------
     734 -        def _on_doc_type_loses_focus(self):
     735                  pk_doc_type = self._PhWheel_doc_type.GetData()
     736                  if pk_doc_type is None:
     737                          self._PRW_doc_comment.unset_context(context = 'pk_doc_type')
     738                  else:
     739                          self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = pk_doc_type)
     740                  return True
     741  #============================================================
     742 -def acquire_images_from_capture_device(device=None, calling_window=None):
     743
     744          _log.debug('acquiring images from [%s]', device)
     745
     746          # do not import globally since we might want to use
     747          # this module without requiring any scanner to be available
     748          from Gnumed.pycommon import gmScanBackend
     749          try:
     750                  fnames = gmScanBackend.acquire_pages_into_files (
     751                          device = device,
     752                          delay = 5,
     753                          calling_window = calling_window
     754                  )
     755          except OSError:
     756                  _log.exception('problem acquiring image from source')
     757                  gmGuiHelpers.gm_show_error (
     758                          aMessage = _(
     759                                  'No images could be acquired from the source.\n\n'
     760                                  'This may mean the scanner driver is not properly installed.\n\n'
     761                                  'On Windows you must install the TWAIN Python module\n'
     762                                  'while on Linux and MacOSX it is recommended to install\n'
     763                                  'the XSane package.'
     764                          ),
     765                          aTitle = _('Acquiring images')
     766                  )
     767                  return None
     768
     769          _log.debug('acquired %s images', len(fnames))
     770
     771          return fnames
     772  #------------------------------------------------------------
     773  from Gnumed.wxGladeWidgets import wxgScanIdxPnl
     774
     775 -class cScanIdxDocsPnl(wxgScanIdxPnl.wxgScanIdxPnl, gmPlugin.cPatientChange_PluginMixin):
     776 -        def __init__(self, *args, **kwds):
     777                  wxgScanIdxPnl.wxgScanIdxPnl.__init__(self, *args, **kwds)
     778                  gmPlugin.cPatientChange_PluginMixin.__init__(self)
     779
     780                  self._PhWheel_reviewer.matcher = gmPerson.cMatchProvider_Provider()
     781
     782                  self.__init_ui_data()
     783                  self._PhWheel_doc_type.add_callback_on_lose_focus(self._on_doc_type_loses_focus)
     784
     785                  # make me and listctrl a file drop target
     786                  dt = gmGuiHelpers.cFileDropTarget(self)
     787                  self.SetDropTarget(dt)
     788                  dt = gmGuiHelpers.cFileDropTarget(self._LBOX_doc_pages)
     789                  self._LBOX_doc_pages.SetDropTarget(dt)
     790                  self._LBOX_doc_pages.add_filenames = self.add_filenames_to_listbox
     791
     792                  # do not import globally since we might want to use
     793                  # this module without requiring any scanner to be available
     794                  from Gnumed.pycommon import gmScanBackend
     795                  self.scan_module = gmScanBackend
     796          #--------------------------------------------------------
     797          # file drop target API
     798          #--------------------------------------------------------
     799 -        def add_filenames_to_listbox(self, filenames):
     800                  self.add_filenames(filenames=filenames)
     801          #--------------------------------------------------------
     802 -        def add_filenames(self, filenames):
     803                  pat = gmPerson.gmCurrentPatient()
     804                  if not pat.connected:
     805                          gmDispatcher.send(signal='statustext', msg=_('Cannot accept new documents. No active patient.'))
     806                          return
     807
     808                  # dive into folders dropped onto us and extract files (one level deep only)
     809                  real_filenames = []
     810                  for pathname in filenames:
     811                          try:
     812                                  files = os.listdir(pathname)
     813                                  gmDispatcher.send(signal='statustext', msg=_('Extracting files from folder [%s] ...') % pathname)
     814                                  for file in files:
     815                                          fullname = os.path.join(pathname, file)
     816                                          if not os.path.isfile(fullname):
     817                                                  continue
     818                                          real_filenames.append(fullname)
     819                          except OSError:
     820                                  real_filenames.append(pathname)
     821
     822                  self.acquired_pages.extend(real_filenames)
     823                  self.__reload_LBOX_doc_pages()
     824          #--------------------------------------------------------
     825 -        def repopulate_ui(self):
     826                  pass
     827          #--------------------------------------------------------
     828          # patient change plugin API
     829          #--------------------------------------------------------
     830 -        def _pre_patient_selection(self, **kwds):
     831                  # FIXME: persist pending data from here
     832                  pass
     833          #--------------------------------------------------------
     834 -        def _post_patient_selection(self, **kwds):
     835                  self.__init_ui_data()
     836          #--------------------------------------------------------
     837          # internal API
     838          #--------------------------------------------------------
     839 -        def __init_ui_data(self):
     840                  # -----------------------------
     841                  self._PhWheel_episode.SetText(value = _('other documents'), suppress_smarts = True)
     842                  self._PhWheel_doc_type.SetText('')
     843                  # -----------------------------
     844                  # FIXME: make this configurable: either now() or last_date()
     845                  fts = gmDateTime.cFuzzyTimestamp()
     846                  self._PhWheel_doc_date.SetText(fts.strftime('%Y-%m-%d'), fts)
     847                  self._PRW_doc_comment.SetText('')
     848                  # FIXME: should be set to patient's primary doc
     849                  self._PhWheel_reviewer.selection_only = True
     850                  me = gmStaff.gmCurrentProvider()
     851                  self._PhWheel_reviewer.SetText (
     852                          value = u'%s (%s%s %s)' % (me['short_alias'], me['title'], me['firstnames'], me['lastnames']),
     853                          data = me['pk_staff']
     854                  )
     855                  # -----------------------------
     856                  # FIXME: set from config item
     857                  self._ChBOX_reviewed.SetValue(False)
     858                  self._ChBOX_abnormal.Disable()
     859                  self._ChBOX_abnormal.SetValue(False)
     860                  self._ChBOX_relevant.Disable()
     861                  self._ChBOX_relevant.SetValue(False)
     862                  # -----------------------------
     863                  self._TBOX_description.SetValue('')
     864                  # -----------------------------
     865                  # the list holding our page files
     866                  self._LBOX_doc_pages.Clear()
     867                  self.acquired_pages = []
     868
     869                  self._PhWheel_doc_type.SetFocus()
     870          #--------------------------------------------------------
     871 -        def __reload_LBOX_doc_pages(self):
     872                  self._LBOX_doc_pages.Clear()
     873                  if len(self.acquired_pages) > 0:
     874                          for i in range(len(self.acquired_pages)):
     875                                  fname = self.acquired_pages[i]
     876                                  self._LBOX_doc_pages.Append(_('part %s: %s') % (i+1, fname), fname)
     877          #--------------------------------------------------------
     878 -        def __valid_for_save(self):
     879                  title = _('saving document')
     880
     881                  if self.acquired_pages is None or len(self.acquired_pages) == 0:
     882                          dbcfg = gmCfg.cCfgSQL()
     883                          allow_empty = bool(dbcfg.get2 (
     884                                  option =  u'horstspace.scan_index.allow_partless_documents',
     885                                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     886                                  bias = 'user',
     887                                  default = False
     888                          ))
     889                          if allow_empty:
     890                                  save_empty = gmGuiHelpers.gm_show_question (
     891                                          aMessage = _('No parts to save. Really save an empty document as a reference ?'),
     892                                          aTitle = title
     893                                  )
     894                                  if not save_empty:
     895                                          return False
     896                          else:
     897                                  gmGuiHelpers.gm_show_error (
     898                                          aMessage = _('No parts to save. Aquire some parts first.'),
     899                                          aTitle = title
     900                                  )
     901                                  return False
     902
     903                  doc_type_pk = self._PhWheel_doc_type.GetData(can_create = True)
     904                  if doc_type_pk is None:
     905                          gmGuiHelpers.gm_show_error (
     906                                  aMessage = _('No document type applied. Choose a document type'),
     907                                  aTitle = title
     908                          )
     909                          return False
     910
     911                  # this should be optional, actually
     912  #               if self._PRW_doc_comment.GetValue().strip() == '':
     913  #                       gmGuiHelpers.gm_show_error (
     914  #                               aMessage = _('No document comment supplied. Add a comment for this document.'),
     915  #                               aTitle = title
     916  #                       )
     917  #                       return False
     918
     919                  if self._PhWheel_episode.GetValue().strip() == '':
     920                          gmGuiHelpers.gm_show_error (
     921                                  aMessage = _('You must select an episode to save this document under.'),
     922                                  aTitle = title
     923                          )
     924                          return False
     925
     926                  if self._PhWheel_reviewer.GetData() is None:
     927                          gmGuiHelpers.gm_show_error (
     928                                  aMessage = _('You need to select from the list of staff members the doctor who is intended to sign the document.'),
     929                                  aTitle = title
     930                          )
     931                          return False
     932
     933                  return True
     934          #--------------------------------------------------------
     935 -        def get_device_to_use(self, reconfigure=False):
     936
     937                  if not reconfigure:
     938                          dbcfg = gmCfg.cCfgSQL()
     939                          device = dbcfg.get2 (
     940                                  option =  'external.xsane.default_device',
     941                                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     942                                  bias = 'workplace',
     943                                  default = ''
     944                          )
     945                          if device.strip() == u'':
     946                                  device = None
     947                          if device is not None:
     948                                  return device
     949
     950                  try:
     951                          devices = self.scan_module.get_devices()
     952                  except:
     953                          _log.exception('cannot retrieve list of image sources')
     954                          gmDispatcher.send(signal = 'statustext', msg = _('There is no scanner support installed on this machine.'))
     955                          return None
     956
     957                  if devices is None:
     958                          # get_devices() not implemented for TWAIN yet
     959                          # XSane has its own chooser (so does TWAIN)
     960                          return None
     961
     962                  if len(devices) == 0:
     963                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot find an active scanner.'))
     964                          return None
     965
     966  #               device_names = []
     967  #               for device in devices:
     968  #                       device_names.append('%s (%s)' % (device[2], device[0]))
     969
     970                  device = gmListWidgets.get_choices_from_list (
     971                          parent = self,
     972                          msg = _('Select an image capture device'),
     973                          caption = _('device selection'),
     974                          choices = [ '%s (%s)' % (d[2], d[0]) for d in devices ],
     975                          columns = [_('Device')],
     976                          data = devices,
     977                          single_selection = True
     978                  )
     979                  if device is None:
     980                          return None
     981
     982                  # FIXME: add support for actually reconfiguring
     983                  return device[0]
     984          #--------------------------------------------------------
     985          # event handling API
     986          #--------------------------------------------------------
     987 -        def _scan_btn_pressed(self, evt):
     988
     989                  chosen_device = self.get_device_to_use()
     990
     991                  # FIXME: configure whether to use XSane or sane directly
     992                  # FIXME: add support for xsane_device_settings argument
     993                  try:
     994                          fnames = self.scan_module.acquire_pages_into_files (
     995                                  device = chosen_device,
     996                                  delay = 5,
     997                                  calling_window = self
     998                          )
     999                  except OSError:
    1000                          _log.exception('problem acquiring image from source')
    1001                          gmGuiHelpers.gm_show_error (
    1002                                  aMessage = _(
    1003                                          'No pages could be acquired from the source.\n\n'
    1004                                          'This may mean the scanner driver is not properly installed.\n\n'
    1005                                          'On Windows you must install the TWAIN Python module\n'
    1006                                          'while on Linux and MacOSX it is recommended to install\n'
    1007                                          'the XSane package.'
    1008                                  ),
    1009                                  aTitle = _('acquiring page')
    1010                          )
    1011                          return None
    1012
    1013                  if len(fnames) == 0:            # no pages scanned
    1014                          return True
    1015
    1016                  self.acquired_pages.extend(fnames)
    1017                  self.__reload_LBOX_doc_pages()
    1018
    1019                  return True
    1020          #--------------------------------------------------------
    1021 -        def _load_btn_pressed(self, evt):
    1022                  # patient file chooser
    1023                  dlg = wx.FileDialog (
    1024                          parent = None,
    1025                          message = _('Choose a file'),
    1026                          defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')),
    1027                          defaultFile = '',
    1028                          wildcard = "%s (*)|*|TIFFs (*.tif)|*.tif|JPEGs (*.jpg)|*.jpg|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')),
    1029                          style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST | wx.MULTIPLE
    1030                  )
    1031                  result = dlg.ShowModal()
    1032                  if result != wx.ID_CANCEL:
    1033                          files = dlg.GetPaths()
    1034                          for file in files:
    1035                                  self.acquired_pages.append(file)
    1036                          self.__reload_LBOX_doc_pages()
    1037                  dlg.Destroy()
    1038          #--------------------------------------------------------
    1039 -        def _show_btn_pressed(self, evt):
    1040                  # did user select a page ?
    1041                  page_idx = self._LBOX_doc_pages.GetSelection()
    1042                  if page_idx == -1:
    1043                          gmGuiHelpers.gm_show_info (
    1044                                  aMessage = _('You must select a part before you can view it.'),
    1045                                  aTitle = _('displaying part')
    1046                          )
    1047                          return None
    1048                  # now, which file was that again ?
    1049                  page_fname = self._LBOX_doc_pages.GetClientData(page_idx)
    1050
    1051                  (result, msg) = gmMimeLib.call_viewer_on_file(page_fname)
    1052                  if not result:
    1053                          gmGuiHelpers.gm_show_warning (
    1054                                  aMessage = _('Cannot display document part:\n%s') % msg,
    1055                                  aTitle = _('displaying part')
    1056                          )
    1057                          return None
    1058                  return 1
    1059          #--------------------------------------------------------
    1060 -        def _del_btn_pressed(self, event):
    1061                  page_idx = self._LBOX_doc_pages.GetSelection()
    1062                  if page_idx == -1:
    1063                          gmGuiHelpers.gm_show_info (
    1064                                  aMessage = _('You must select a part before you can delete it.'),
    1065                                  aTitle = _('deleting part')
    1066                          )
    1067                          return None
    1068                  page_fname = self._LBOX_doc_pages.GetClientData(page_idx)
    1069
    1070                  # 1) del item from self.acquired_pages
    1071                  self.acquired_pages[page_idx:(page_idx+1)] = []
    1072
    1073                  # 2) reload list box
    1074                  self.__reload_LBOX_doc_pages()
    1075
    1076                  # 3) optionally kill file in the file system
    1077                  do_delete = gmGuiHelpers.gm_show_question (
    1078                          _('The part has successfully been removed from the document.\n'
    1079                            '\n'
    1080                            'Do you also want to permanently delete the file\n'
    1081                            '\n'
    1082                            ' [%s]\n'
    1083                            '\n'
    1084                            'from which this document part was loaded ?\n'
    1085                            '\n'
    1086                            'If it is a temporary file for a page you just scanned\n'
    1087                            'this makes a lot of sense. In other cases you may not\n'
    1088                            'want to lose the file.\n'
    1089                            '\n'
    1090                            'Pressing [YES] will permanently remove the file\n'
    1091                            'from your computer.\n'
    1092                          ) % page_fname,
    1093                          _('Removing document part')
    1094                  )
    1095                  if do_delete:
    1096                          try:
    1097                                  os.remove(page_fname)
    1098                          except:
    1099                                  _log.exception('Error deleting file.')
    1100                                  gmGuiHelpers.gm_show_error (
    1101                                          aMessage = _('Cannot delete part in file [%s].\n\nYou may not have write access to it.') % page_fname,
    1102                                          aTitle = _('deleting part')
    1103                                  )
    1104
    1105                  return 1
    1106          #--------------------------------------------------------
    1107 -        def _save_btn_pressed(self, evt):
    1108
    1109                  if not self.__valid_for_save():
    1110                          return False
    1111
    1112                  wx.BeginBusyCursor()
    1113
    1114                  pat = gmPerson.gmCurrentPatient()
    1115                  doc_folder = pat.get_document_folder()
    1116                  emr = pat.get_emr()
    1117
    1118                  # create new document
    1119                  pk_episode = self._PhWheel_episode.GetData()
    1120                  if pk_episode is None:
    1121                          episode = emr.add_episode (
    1122                                  episode_name = self._PhWheel_episode.GetValue().strip(),
    1123                                  is_open = True
    1124                          )
    1125                          if episode is None:
    1126                                  wx.EndBusyCursor()
    1127                                  gmGuiHelpers.gm_show_error (
    1128                                          aMessage = _('Cannot start episode [%s].') % self._PhWheel_episode.GetValue().strip(),
    1129                                          aTitle = _('saving document')
    1130                                  )
    1131                                  return False
    1132                          pk_episode = episode['pk_episode']
    1133
    1134                  encounter = emr.active_encounter['pk_encounter']
    1135                  document_type = self._PhWheel_doc_type.GetData()
    1136                  new_doc = doc_folder.add_document(document_type, encounter, pk_episode)
    1137                  if new_doc is None:
    1138                          wx.EndBusyCursor()
    1139                          gmGuiHelpers.gm_show_error (
    1140                                  aMessage = _('Cannot create new document.'),
    1141                                  aTitle = _('saving document')
    1142                          )
    1143                          return False
    1144
    1145                  # update business object with metadata
    1146                  # - date of generation
    1147                  new_doc['clin_when'] = self._PhWheel_doc_date.GetData().get_pydt()
    1148                  # - external reference
    1149                  cfg = gmCfg.cCfgSQL()
    1150                  generate_uuid = bool (
    1151                          cfg.get2 (
    1152                                  option = 'horstspace.scan_index.generate_doc_uuid',
    1153                                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1154                                  bias = 'user',
    1155                                  default = False
    1156                          )
    1157                  )
    1158                  ref = None
    1159                  if generate_uuid:
    1160                          ref = gmDocuments.get_ext_ref()
    1161                  if ref is not None:
    1162                          new_doc['ext_ref'] = ref
    1163                  # - comment
    1164                  comment = self._PRW_doc_comment.GetLineText(0).strip()
    1165                  if comment != u'':
    1166                          new_doc['comment'] = comment
    1167                  # - save it
    1168                  if not new_doc.save_payload():
    1169                          wx.EndBusyCursor()
    1170                          gmGuiHelpers.gm_show_error (
    1171                                  aMessage = _('Cannot update document metadata.'),
    1172                                  aTitle = _('saving document')
    1173                          )
    1174                          return False
    1175                  # - long description
    1176                  description = self._TBOX_description.GetValue().strip()
    1177                  if description != '':
    1178                          if not new_doc.add_description(description):
    1179                                  wx.EndBusyCursor()
    1180                                  gmGuiHelpers.gm_show_error (
    1181                                          aMessage = _('Cannot add document description.'),
    1182                                          aTitle = _('saving document')
    1183                                  )
    1184                                  return False
    1185
    1186                  # add document parts from files
    1187                  success, msg, filename = new_doc.add_parts_from_files (
    1188                          files = self.acquired_pages,
    1189                          reviewer = self._PhWheel_reviewer.GetData()
    1190                  )
    1191                  if not success:
    1192                          wx.EndBusyCursor()
    1193                          gmGuiHelpers.gm_show_error (
    1194                                  aMessage = msg,
    1195                                  aTitle = _('saving document')
    1196                          )
    1197                          return False
    1198
    1199                  # set reviewed status
    1200                  if self._ChBOX_reviewed.GetValue():
    1201                          if not new_doc.set_reviewed (
    1202                                  technically_abnormal = self._ChBOX_abnormal.GetValue(),
    1203                                  clinically_relevant = self._ChBOX_relevant.GetValue()
    1204                          ):
    1205                                  msg = _('Error setting "reviewed" status of new document.')
    1206
    1207                  gmHooks.run_hook_script(hook = u'after_new_doc_created')
    1208
    1209                  # inform user
    1210                  show_id = bool (
    1211                          cfg.get2 (
    1212                                  option = 'horstspace.scan_index.show_doc_id',
    1213                                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1214                                  bias = 'user'
    1215                          )
    1216                  )
    1217                  wx.EndBusyCursor()
    1218                  if show_id:
    1219                          if ref is None:
    1220                                  msg = _('Successfully saved the new document.')
    1221                          else:
    1222                                  msg = _(
    1223  """The reference ID for the new document is:
    1224
    1225   <%s>
    1226
    1227  You probably want to write it down on the
    1228  original documents.
    1229
    1230  If you don't care about the ID you can switch
    1231  off this message in the GNUmed configuration.""") % ref
    1232                          gmGuiHelpers.gm_show_info (
    1233                                  aMessage = msg,
    1234                                  aTitle = _('Saving document')
    1235                          )
    1236                  else:
    1237                          gmDispatcher.send(signal='statustext', msg=_('Successfully saved new document.'))
    1238
    1239                  self.__init_ui_data()
    1240                  return True
    1241          #--------------------------------------------------------
    1242 -        def _startover_btn_pressed(self, evt):
    1243                  self.__init_ui_data()
    1244          #--------------------------------------------------------
    1245 -        def _reviewed_box_checked(self, evt):
    1246                  self._ChBOX_abnormal.Enable(enable = self._ChBOX_reviewed.GetValue())
    1247                  self._ChBOX_relevant.Enable(enable = self._ChBOX_reviewed.GetValue())
    1248          #--------------------------------------------------------
    1249 -        def _on_doc_type_loses_focus(self):
    1250                  pk_doc_type = self._PhWheel_doc_type.GetData()
    1251                  if pk_doc_type is None:
    1252                          self._PRW_doc_comment.unset_context(context = 'pk_doc_type')
    1253                  else:
    1254                          self._PRW_doc_comment.set_context(context = 'pk_doc_type', val = pk_doc_type)
    1255                  return True
    1256  #============================================================
    1257 -def display_document_part(parent=None, part=None):
    1258
    1259          if parent is None:
    1260                  parent = wx.GetApp().GetTopWindow()
    1261
    1262          # sanity check
    1263          if part['size'] == 0:
    1264                  _log.debug('cannot display part [%s] - 0 bytes', part['pk_obj'])
    1265                  gmGuiHelpers.gm_show_error (
    1266                          aMessage = _('Document part does not seem to exist in database !'),
    1267                          aTitle = _('showing document')
    1268                  )
    1269                  return None
    1270
    1271          wx.BeginBusyCursor()
    1272          cfg = gmCfg.cCfgSQL()
    1273
    1274          # determine database export chunk size
    1275          chunksize = int(
    1276          cfg.get2 (
    1277                  option = "horstspace.blob_export_chunk_size",
    1278                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1279                  bias = 'workplace',
    1280                  default = 2048
    1281          ))
    1282
    1283          # shall we force blocking during view ?
    1284          block_during_view = bool( cfg.get2 (
    1285                  option = 'horstspace.document_viewer.block_during_view',
    1286                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1287                  bias = 'user',
    1288                  default = None
    1289          ))
    1290
    1291          wx.EndBusyCursor()
    1292
    1293          # display it
    1294          successful, msg = part.display_via_mime (
    1295                  chunksize = chunksize,
    1296                  block = block_during_view
    1297          )
    1298          if not successful:
    1299                  gmGuiHelpers.gm_show_error (
    1300                          aMessage = _('Cannot display document part:\n%s') % msg,
    1301                          aTitle = _('showing document')
    1302                  )
    1303                  return None
    1304
    1305          # handle review after display
    1306          # 0: never
    1307          # 1: always
    1308          # 2: if no review by myself exists yet
    1309          # 3: if no review at all exists yet
    1310          # 4: if no review by responsible reviewer
    1311          review_after_display = int(cfg.get2 (
    1312                  option = 'horstspace.document_viewer.review_after_display',
    1313                  workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1314                  bias = 'user',
    1315                  default = 3
    1316          ))
    1317          if review_after_display == 1:                   # always review
    1318                  review_document_part(parent = parent, part = part)
    1319          elif review_after_display == 2:                 # review if no review by me exists
    1320                  review_by_me = filter(lambda rev: rev['is_your_review'], part.get_reviews())
    1321                  if len(review_by_me) == 0:
    1322                          review_document_part(parent = parent, part = part)
    1323          elif review_after_display == 3:
    1324                  if len(part.get_reviews()) == 0:
    1325                          review_document_part(parent = parent, part = part)
    1326          elif review_after_display == 4:
    1327                  reviewed_by_responsible = filter(lambda rev: rev['is_review_by_responsible_reviewer'], part.get_reviews())
    1328                  if len(reviewed_by_responsible) == 0:
    1329                          review_document_part(parent = parent, part = part)
    1330
    1331          return True
    1332  #============================================================
    1333 -def manage_documents(parent=None, msg=None, single_selection=True):
    1334
    1335          pat = gmPerson.gmCurrentPatient()
    1336
    1337          if parent is None:
    1338                  parent = wx.GetApp().GetTopWindow()
    1339          #--------------------------------------------------------
    1340          def edit(document=None):
    1341                  return
    1342                  #return edit_consumable_substance(parent = parent, substance = substance, single_entry = (substance is not None))
    1343          #--------------------------------------------------------
    1344          def delete(document):
    1345                  return
    1346  #               if substance.is_in_use_by_patients:
    1347  #                       gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this substance. It is in use.'), beep = True)
    1348  #                       return False
    1349  #
    1350  #               return gmMedication.delete_consumable_substance(substance = substance['pk'])
    1351          #------------------------------------------------------------
    1352          def refresh(lctrl):
    1353                  docs = pat.document_folder.get_documents()
    1354                  items = [ [
    1355                          gmDateTime.pydt_strftime(d['clin_when'], u'%Y %b %d', accuracy = gmDateTime.acc_days),
    1356                          d['l10n_type'],
    1357                          gmTools.coalesce(d['comment'], u''),
    1358                          gmTools.coalesce(d['ext_ref'], u''),
    1359                          d['pk_doc']
    1360                  ] for d in docs ]
    1361                  lctrl.set_string_items(items)
    1362                  lctrl.set_data(docs)
    1363          #------------------------------------------------------------
    1364          if msg is None:
    1365                  msg = _('Document list for this patient.')
    1366          return gmListWidgets.get_choices_from_list (
    1367                  parent = parent,
    1368                  msg = msg,
    1369                  caption = _('Showing documents.'),
    1370                  columns = [_('Generated'), _('Type'), _('Comment'), _('Ref #'), u'#'],
    1371                  single_selection = single_selection,
    1372                  #new_callback = edit,
    1373                  #edit_callback = edit,
    1374                  #delete_callback = delete,
    1375                  refresh_callback = refresh
    1376                  #,left_extra_button = (_('Import'), _('Import consumable substances from a drug database.'), add_from_db)
    1377          )
    1378  #============================================================
    1379  from Gnumed.wxGladeWidgets import wxgSelectablySortedDocTreePnl
    1380
    1381 -class cSelectablySortedDocTreePnl(wxgSelectablySortedDocTreePnl.wxgSelectablySortedDocTreePnl):
    1382          """A panel with a document tree which can be sorted."""
    1383          #--------------------------------------------------------
    1384          # inherited event handlers
    1385          #--------------------------------------------------------
    1386 -        def _on_sort_by_age_selected(self, evt):
    1387                  self._doc_tree.sort_mode = 'age'
    1388                  self._doc_tree.SetFocus()
    1389                  self._rbtn_sort_by_age.SetValue(True)
    1390          #--------------------------------------------------------
    1391 -        def _on_sort_by_review_selected(self, evt):
    1392                  self._doc_tree.sort_mode = 'review'
    1393                  self._doc_tree.SetFocus()
    1394                  self._rbtn_sort_by_review.SetValue(True)
    1395          #--------------------------------------------------------
    1396 -        def _on_sort_by_episode_selected(self, evt):
    1397                  self._doc_tree.sort_mode = 'episode'
    1398                  self._doc_tree.SetFocus()
    1399                  self._rbtn_sort_by_episode.SetValue(True)
    1400          #--------------------------------------------------------
    1401 -        def _on_sort_by_issue_selected(self, event):
    1402                  self._doc_tree.sort_mode = 'issue'
    1403                  self._doc_tree.SetFocus()
    1404                  self._rbtn_sort_by_issue.SetValue(True)
    1405          #--------------------------------------------------------
    1406 -        def _on_sort_by_type_selected(self, evt):
    1407                  self._doc_tree.sort_mode = 'type'
    1408                  self._doc_tree.SetFocus()
    1409                  self._rbtn_sort_by_type.SetValue(True)
    1410  #============================================================
    1411 -class cDocTree(wx.TreeCtrl, gmRegetMixin.cRegetOnPaintMixin):
    1412          # FIXME: handle expansion state
    1413          """This wx.TreeCtrl derivative displays a tree view of stored medical documents.
    1414
    1415          It listens to document and patient changes and updates itself accordingly.
    1416
    1417          This acts on the current patient.
    1418          """
    1419          _sort_modes = ['age', 'review', 'episode', 'type', 'issue']
    1420          _root_node_labels = None
    1421          #--------------------------------------------------------
    1422 -        def __init__(self, parent, id, *args, **kwds):
    1423                  """Set up our specialised tree.
    1424                  """
    1425                  kwds['style'] = wx.TR_NO_BUTTONS | wx.NO_BORDER | wx.TR_SINGLE
    1426                  wx.TreeCtrl.__init__(self, parent, id, *args, **kwds)
    1427
    1428                  gmRegetMixin.cRegetOnPaintMixin.__init__(self)
    1429
    1430                  tmp = _('available documents (%s)')
    1431                  unsigned = _('unsigned (%s) on top') % u'\u270D'
    1432                  cDocTree._root_node_labels = {
    1433                          'age': tmp % _('most recent on top'),
    1434                          'review': tmp % unsigned,
    1435                          'episode': tmp % _('sorted by episode'),
    1436                          'issue': tmp % _('sorted by health issue'),
    1437                          'type': tmp % _('sorted by type')
    1438                  }
    1439
    1440                  self.root = None
    1441                  self.__sort_mode = 'age'
    1442
    1443                  self.__build_context_menus()
    1444                  self.__register_interests()
    1445                  self._schedule_data_reget()
    1446          #--------------------------------------------------------
    1447          # external API
    1448          #--------------------------------------------------------
    1449 -        def display_selected_part(self, *args, **kwargs):
    1450
    1451                  node = self.GetSelection()
    1452                  node_data = self.GetPyData(node)
    1453
    1454                  if not isinstance(node_data, gmDocuments.cDocumentPart):
    1455                          return True
    1456
    1457                  self.__display_part(part = node_data)
    1458                  return True
    1459          #--------------------------------------------------------
    1460          # properties
    1461          #--------------------------------------------------------
    1462 -        def _get_sort_mode(self):
    1463                  return self.__sort_mode
    1464          #-----
    1465 -        def _set_sort_mode(self, mode):
    1466                  if mode is None:
    1467                          mode = 'age'
    1468
    1469                  if mode == self.__sort_mode:
    1470                          return
    1471
    1472                  if mode not in cDocTree._sort_modes:
    1473                          raise ValueError('invalid document tree sort mode [%s], valid modes: %s' % (mode, cDocTree._sort_modes))
    1474
    1475                  self.__sort_mode = mode
    1476
    1477                  curr_pat = gmPerson.gmCurrentPatient()
    1478                  if not curr_pat.connected:
    1479                          return
    1480
    1481                  self._schedule_data_reget()
    1482          #-----
    1483          sort_mode = property(_get_sort_mode, _set_sort_mode)
    1484          #--------------------------------------------------------
    1485          # reget-on-paint API
    1486          #--------------------------------------------------------
    1487 -        def _populate_with_data(self):
    1488                  curr_pat = gmPerson.gmCurrentPatient()
    1489                  if not curr_pat.connected:
    1490                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot load documents. No active patient.'))
    1491                          return False
    1492
    1493                  if not self.__populate_tree():
    1494                          return False
    1495
    1496                  return True
    1497          #--------------------------------------------------------
    1498          # internal helpers
    1499          #--------------------------------------------------------
    1500 -        def __register_interests(self):
    1501                  # connect handlers
    1502                  wx.EVT_TREE_ITEM_ACTIVATED (self, self.GetId(), self._on_activate)
    1503                  wx.EVT_TREE_ITEM_RIGHT_CLICK (self, self.GetId(), self.__on_right_click)
    1504                  wx.EVT_TREE_ITEM_GETTOOLTIP(self, -1, self._on_tree_item_gettooltip)
    1505
    1506  #                wx.EVT_LEFT_DCLICK(self.tree, self.OnLeftDClick)
    1507
    1508                  gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
    1509                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
    1510                  gmDispatcher.connect(signal = u'blobs.doc_med_mod_db', receiver = self._on_doc_mod_db)
    1511                  gmDispatcher.connect(signal = u'blobs.doc_obj_mod_db', receiver = self._on_doc_page_mod_db)
    1512          #--------------------------------------------------------
    1513 -        def __build_context_menus(self):
    1514
    1515                  # --- part context menu ---
    1516                  self.__part_context_menu = wx.Menu(title = _('Part Actions:'))
    1517
    1518                  ID = wx.NewId()
    1519                  self.__part_context_menu.Append(ID, _('Display part'))
    1520                  wx.EVT_MENU(self.__part_context_menu, ID, self.__display_curr_part)
    1521
    1522                  ID = wx.NewId()
    1523                  self.__part_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D')
    1524                  wx.EVT_MENU(self.__part_context_menu, ID, self.__review_curr_part)
    1525
    1526                  self.__part_context_menu.AppendSeparator()
    1527
    1528                  item = self.__part_context_menu.Append(-1, _('Delete part'))
    1529                  self.Bind(wx.EVT_MENU, self.__delete_part, item)
    1530
    1531                  item = self.__part_context_menu.Append(-1, _('Move part'))
    1532                  self.Bind(wx.EVT_MENU, self.__move_part, item)
    1533
    1534                  ID = wx.NewId()
    1535                  self.__part_context_menu.Append(ID, _('Print part'))
    1536                  wx.EVT_MENU(self.__part_context_menu, ID, self.__print_part)
    1537
    1538                  ID = wx.NewId()
    1539                  self.__part_context_menu.Append(ID, _('Fax part'))
    1540                  wx.EVT_MENU(self.__part_context_menu, ID, self.__fax_part)
    1541
    1542                  ID = wx.NewId()
    1543                  self.__part_context_menu.Append(ID, _('Mail part'))
    1544                  wx.EVT_MENU(self.__part_context_menu, ID, self.__mail_part)
    1545
    1546                  self.__part_context_menu.AppendSeparator()                      # so we can append some items
    1547
    1548                  # --- doc context menu ---
    1549                  self.__doc_context_menu = wx.Menu(title = _('Document Actions:'))
    1550
    1551                  ID = wx.NewId()
    1552                  self.__doc_context_menu.Append(ID, _('%s Sign/Edit properties') % u'\u270D')
    1553                  wx.EVT_MENU(self.__doc_context_menu, ID, self.__review_curr_part)
    1554
    1555                  self.__doc_context_menu.AppendSeparator()
    1556
    1557                  item = self.__doc_context_menu.Append(-1, _('Add parts'))
    1558                  self.Bind(wx.EVT_MENU, self.__add_part, item)
    1559
    1560                  ID = wx.NewId()
    1561                  self.__doc_context_menu.Append(ID, _('Print all parts'))
    1562                  wx.EVT_MENU(self.__doc_context_menu, ID, self.__print_doc)
    1563
    1564                  ID = wx.NewId()
    1565                  self.__doc_context_menu.Append(ID, _('Fax all parts'))
    1566                  wx.EVT_MENU(self.__doc_context_menu, ID, self.__fax_doc)
    1567
    1568                  ID = wx.NewId()
    1569                  self.__doc_context_menu.Append(ID, _('Mail all parts'))
    1570                  wx.EVT_MENU(self.__doc_context_menu, ID, self.__mail_doc)
    1571
    1572                  ID = wx.NewId()
    1573                  self.__doc_context_menu.Append(ID, _('Export all parts'))
    1574                  wx.EVT_MENU(self.__doc_context_menu, ID, self.__export_doc_to_disk)
    1575
    1576                  self.__doc_context_menu.AppendSeparator()
    1577
    1578                  ID = wx.NewId()
    1579                  self.__doc_context_menu.Append(ID, _('Delete document'))
    1580                  wx.EVT_MENU(self.__doc_context_menu, ID, self.__delete_document)
    1581
    1582                  ID = wx.NewId()
    1583                  self.__doc_context_menu.Append(ID, _('Access external original'))
    1584                  wx.EVT_MENU(self.__doc_context_menu, ID, self.__access_external_original)
    1585
    1586                  ID = wx.NewId()
    1587                  self.__doc_context_menu.Append(ID, _('Edit corresponding encounter'))
    1588                  wx.EVT_MENU(self.__doc_context_menu, ID, self.__edit_encounter_details)
    1589
    1590                  ID = wx.NewId()
    1591                  self.__doc_context_menu.Append(ID, _('Select corresponding encounter'))
    1592                  wx.EVT_MENU(self.__doc_context_menu, ID, self.__select_encounter)
    1593
    1594  #               self.__doc_context_menu.AppendSeparator()
    1595
    1596                  ID = wx.NewId()
    1597                  self.__doc_context_menu.Append(ID, _('Manage descriptions'))
    1598                  wx.EVT_MENU(self.__doc_context_menu, ID, self.__manage_document_descriptions)
    1599
    1600                  # document / description
    1601  #               self.__desc_menu = wx.Menu()
    1602  #               ID = wx.NewId()
    1603  #               self.__doc_context_menu.AppendMenu(ID, _('Descriptions ...'), self.__desc_menu)
    1604
    1605  #               ID = wx.NewId()
    1606  #               self.__desc_menu.Append(ID, _('Add new description'))
    1607  #               wx.EVT_MENU(self.__desc_menu, ID, self.__add_doc_desc)
    1608
    1609  #               ID = wx.NewId()
    1610  #               self.__desc_menu.Append(ID, _('Delete description'))
    1611  #               wx.EVT_MENU(self.__desc_menu, ID, self.__del_doc_desc)
    1612
    1613  #               self.__desc_menu.AppendSeparator()
    1614          #--------------------------------------------------------
    1615 -        def __populate_tree(self):
    1616
    1617                  wx.BeginBusyCursor()
    1618
    1619                  # clean old tree
    1620                  if self.root is not None:
    1621                          self.DeleteAllItems()
    1622
    1623                  # init new tree
    1624                  self.root = self.AddRoot(cDocTree._root_node_labels[self.__sort_mode], -1, -1)
    1625                  self.SetItemPyData(self.root, None)
    1626                  self.SetItemHasChildren(self.root, False)
    1627
    1628                  # read documents from database
    1629                  curr_pat = gmPerson.gmCurrentPatient()
    1630                  docs_folder = curr_pat.get_document_folder()
    1631                  docs = docs_folder.get_documents()
    1632
    1633                  if docs is None:
    1634                          gmGuiHelpers.gm_show_error (
    1635                                  aMessage = _('Error searching documents.'),
    1636                                  aTitle = _('loading document list')
    1637                          )
    1638                          # avoid recursion of GUI updating
    1639                          wx.EndBusyCursor()
    1640                          return True
    1641
    1642                  if len(docs) == 0:
    1643                          wx.EndBusyCursor()
    1644                          return True
    1645
    1646                  # fill new tree from document list
    1647                  self.SetItemHasChildren(self.root, True)
    1648
    1649                  # add our documents as first level nodes
    1650                  intermediate_nodes = {}
    1651                  for doc in docs:
    1652
    1653                          parts = doc.parts
    1654
    1655                          if len(parts) == 0:
    1656                                  no_parts = _('no parts')
    1657                          elif len(parts) == 1:
    1658                                  no_parts = _('1 part')
    1659                          else:
    1660                                  no_parts = _('%s parts') % len(parts)
    1661
    1662                          # need intermediate branch level ?
    1663                          if self.__sort_mode == 'episode':
    1664                                  inter_label = u'%s%s' % (doc['episode'], gmTools.coalesce(doc['health_issue'], u'', u' (%s)'))
    1665                                  doc_label = _('%s%7s %s:%s (%s)') % (
    1666                                          gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'),
    1667                                          doc['clin_when'].strftime('%m/%Y'),
    1668                                          doc['l10n_type'][:26],
    1669                                          gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'),
    1670                                          no_parts
    1671                                  )
    1672                                  if not intermediate_nodes.has_key(inter_label):
    1673                                          intermediate_nodes[inter_label] = self.AppendItem(parent = self.root, text = inter_label)
    1674                                          self.SetItemBold(intermediate_nodes[inter_label], bold = True)
    1675                                          self.SetItemPyData(intermediate_nodes[inter_label], None)
    1676                                          self.SetItemHasChildren(intermediate_nodes[inter_label], True)
    1677                                  parent = intermediate_nodes[inter_label]
    1678
    1679                          elif self.__sort_mode == 'type':
    1680                                  inter_label = doc['l10n_type']
    1681                                  doc_label = _('%s%7s (%s):%s (%s)') % (
    1682                                          gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'),
    1683                                          doc['clin_when'].strftime('%m/%Y'),
    1684                                          no_parts,
    1685                                          gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'),
    1686                                          u'%s%s' % (doc['episode'], gmTools.coalesce(doc['health_issue'], u'', u' %s %%s' % gmTools.u_right_arrow))
    1687                                  )
    1688                                  if not intermediate_nodes.has_key(inter_label):
    1689                                          intermediate_nodes[inter_label] = self.AppendItem(parent = self.root, text = inter_label)
    1690                                          self.SetItemBold(intermediate_nodes[inter_label], bold = True)
    1691                                          self.SetItemPyData(intermediate_nodes[inter_label], None)
    1692                                          self.SetItemHasChildren(intermediate_nodes[inter_label], True)
    1693                                  parent = intermediate_nodes[inter_label]
    1694
    1695                          elif self.__sort_mode == 'issue':
    1696                                  if doc['health_issue'] is None:
    1697                                          inter_label = _('%s (unattributed episode)') % doc['episode']
    1698                                  else:
    1699                                          inter_label = doc['health_issue']
    1700                                  doc_label = _('%s%7s %s:%s (%s)') % (
    1701                                          gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'),
    1702                                          doc['clin_when'].strftime('%m/%Y'),
    1703                                          doc['l10n_type'][:26],
    1704                                          gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'),
    1705                                          no_parts
    1706                                  )
    1707                                  if not intermediate_nodes.has_key(inter_label):
    1708                                          intermediate_nodes[inter_label] = self.AppendItem(parent = self.root, text = inter_label)
    1709                                          self.SetItemBold(intermediate_nodes[inter_label], bold = True)
    1710                                          self.SetItemPyData(intermediate_nodes[inter_label], None)
    1711                                          self.SetItemHasChildren(intermediate_nodes[inter_label], True)
    1712                                  parent = intermediate_nodes[inter_label]
    1713
    1714                          else:
    1715                                  doc_label = _('%s%7s %s:%s (%s)') % (
    1716                                          gmTools.bool2subst(doc.has_unreviewed_parts, gmTools.u_writing_hand, u'', u'?'),
    1717                                          doc['clin_when'].strftime('%m/%Y'),
    1718                                          doc['l10n_type'][:26],
    1719                                          gmTools.coalesce(initial = doc['comment'], instead = u'', template_initial = u' %s'),
    1720                                          no_parts
    1721                                  )
    1722                                  parent = self.root
    1723
    1724                          doc_node = self.AppendItem(parent = parent, text = doc_label)
    1725                          #self.SetItemBold(doc_node, bold = True)
    1726                          self.SetItemPyData(doc_node, doc)
    1727                          if len(parts) == 0:
    1728                                  self.SetItemHasChildren(doc_node, False)
    1729                          else:
    1730                                  self.SetItemHasChildren(doc_node, True)
    1731
    1732                          # now add parts as child nodes
    1733                          for part in parts:
    1734  #                               if part['clinically_relevant']:
    1735  #                                       rel = ' [%s]' % _('Cave')
    1736  #                               else:
    1737  #                                       rel = ''
    1738                                  f_ext = u''
    1739                                  if part['filename'] is not None:
    1740                                          f_ext = os.path.splitext(part['filename'])[1].strip('.').strip()
    1741                                  if f_ext != u'':
    1742                                          f_ext = u' .' + f_ext.upper()
    1743                                  label = '%s%s (%s%s)%s' % (
    1744                                          gmTools.bool2str (
    1745                                                  boolean = part['reviewed'] or part['reviewed_by_you'] or part['reviewed_by_intended_reviewer'],
    1746                                                  true_str = u'',
    1747                                                  false_str = gmTools.u_writing_hand
    1748                                          ),
    1749                                          _('part %2s') % part['seq_idx'],
    1750                                          gmTools.size2str(part['size']),
    1751                                          f_ext,
    1752                                          gmTools.coalesce (
    1753                                                  part['obj_comment'],
    1754                                                  u'',
    1755                                                  u': %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote)
    1756                                          )
    1757                                  )
    1758
    1759                                  part_node = self.AppendItem(parent = doc_node, text = label)
    1760                                  self.SetItemPyData(part_node, part)
    1761                                  self.SetItemHasChildren(part_node, False)
    1762
    1763                  self.__sort_nodes()
    1764                  self.SelectItem(self.root)
    1765
    1766                  # FIXME: apply expansion state if available or else ...
    1767                  # FIXME: ... uncollapse to default state
    1768                  self.Expand(self.root)
    1769                  if self.__sort_mode in ['episode', 'type', 'issue']:
    1770                          for key in intermediate_nodes.keys():
    1771                                  self.Expand(intermediate_nodes[key])
    1772
    1773                  wx.EndBusyCursor()
    1774
    1775                  return True
    1776          #------------------------------------------------------------------------
    1777 -        def OnCompareItems (self, node1=None, node2=None):
    1778                  """Used in sorting items.
    1779
    1780                  -1: 1 < 2
    1781                   0: 1 = 2
    1782                   1: 1 > 2
    1783                  """
    1784                  # Windows can send bogus events so ignore that
    1785                  if not node1:
    1786                          _log.debug('invalid node 1')
    1787                          return 0
    1788                  if not node2:
    1789                          _log.debug('invalid node 2')
    1790                          return 0
    1791                  if not node1.IsOk():
    1792                          _log.debug('no data on node 1')
    1793                          return 0
    1794                  if not node2.IsOk():
    1795                          _log.debug('no data on node 2')
    1796                          return 0
    1797
    1798                  data1 = self.GetPyData(node1)
    1799                  data2 = self.GetPyData(node2)
    1800
    1801                  # doc node
    1802                  if isinstance(data1, gmDocuments.cDocument):
    1803
    1804                          date_field = 'clin_when'
    1805                          #date_field = 'modified_when'
    1806
    1807                          if self.__sort_mode == 'age':
    1808                                  # reverse sort by date
    1809                                  if data1[date_field] > data2[date_field]:
    1810                                          return -1
    1811                                  if data1[date_field] == data2[date_field]:
    1812                                          return 0
    1813                                  return 1
    1814
    1815                          elif self.__sort_mode == 'episode':
    1816                                  if data1['episode'] < data2['episode']:
    1817                                          return -1
    1818                                  if data1['episode'] == data2['episode']:
    1819                                          # inner sort: reverse by date
    1820                                          if data1[date_field] > data2[date_field]:
    1821                                                  return -1
    1822                                          if data1[date_field] == data2[date_field]:
    1823                                                  return 0
    1824                                          return 1
    1825                                  return 1
    1826
    1827                          elif self.__sort_mode == 'issue':
    1828                                  if data1['health_issue'] < data2['health_issue']:
    1829                                          return -1
    1830                                  if data1['health_issue'] == data2['health_issue']:
    1831                                          # inner sort: reverse by date
    1832                                          if data1[date_field] > data2[date_field]:
    1833                                                  return -1
    1834                                          if data1[date_field] == data2[date_field]:
    1835                                                  return 0
    1836                                          return 1
    1837                                  return 1
    1838
    1839                          elif self.__sort_mode == 'review':
    1840                                  # equality
    1841                                  if data1.has_unreviewed_parts == data2.has_unreviewed_parts:
    1842                                          # inner sort: reverse by date
    1843                                          if data1[date_field] > data2[date_field]:
    1844                                                  return -1
    1845                                          if data1[date_field] == data2[date_field]:
    1846                                                  return 0
    1847                                          return 1
    1848                                  if data1.has_unreviewed_parts:
    1849                                          return -1
    1850                                  return 1
    1851
    1852                          elif self.__sort_mode == 'type':
    1853                                  if data1['l10n_type'] < data2['l10n_type']:
    1854                                          return -1
    1855                                  if data1['l10n_type'] == data2['l10n_type']:
    1856                                          # inner sort: reverse by date
    1857                                          if data1[date_field] > data2[date_field]:
    1858                                                  return -1
    1859                                          if data1[date_field] == data2[date_field]:
    1860                                                  return 0
    1861                                          return 1
    1862                                  return 1
    1863
    1864                          else:
    1865                                  _log.error('unknown document sort mode [%s], reverse-sorting by age', self.__sort_mode)
    1866                                  # reverse sort by date
    1867                                  if data1[date_field] > data2[date_field]:
    1868                                          return -1
    1869                                  if data1[date_field] == data2[date_field]:
    1870                                          return 0
    1871                                  return 1
    1872
    1873                  # part node
    1874                  if isinstance(data1, gmDocuments.cDocumentPart):
    1875                          # compare sequence IDs (= "page" numbers)
    1876                          # FIXME: wrong order ?
    1877                          if data1['seq_idx'] < data2['seq_idx']:
    1878                                  return -1
    1879                          if data1['seq_idx'] == data2['seq_idx']:
    1880                                  return 0
    1881                          return 1
    1882
    1883                  # else sort alphabetically
    1884                  if None in [data1, data2]:
    1885                          l1 = self.GetItemText(node1)
    1886                          l2 = self.GetItemText(node2)
    1887                          if l1 < l2:
    1888                                  return -1
    1889                          if l1 == l2:
    1890                                  return 0
    1891                  else:
    1892                          if data1 < data2:
    1893                                  return -1
    1894                          if data1 == data2:
    1895                                  return 0
    1896                  return 1
    1897          #------------------------------------------------------------------------
    1898          # event handlers
    1899          #------------------------------------------------------------------------
    1900 -        def _on_doc_mod_db(self, *args, **kwargs):
    1901                  # FIXME: remember current expansion state
    1902                  wx.CallAfter(self._schedule_data_reget)
    1903          #------------------------------------------------------------------------
    1904 -        def _on_doc_page_mod_db(self, *args, **kwargs):
    1905                  # FIXME: remember current expansion state
    1906                  wx.CallAfter(self._schedule_data_reget)
    1907          #------------------------------------------------------------------------
    1908 -        def _on_pre_patient_selection(self, *args, **kwargs):
    1909                  # FIXME: self.__store_expansion_history_in_db
    1910
    1911                  # empty out tree
    1912                  if self.root is not None:
    1913                          self.DeleteAllItems()
    1914                  self.root = None
    1915          #------------------------------------------------------------------------
    1916 -        def _on_post_patient_selection(self, *args, **kwargs):
    1917                  # FIXME: self.__load_expansion_history_from_db (but not apply it !)
    1918                  self._schedule_data_reget()
    1919          #------------------------------------------------------------------------
    1920 -        def _on_activate(self, event):
    1921                  node = event.GetItem()
    1922                  node_data = self.GetPyData(node)
    1923
    1924                  # exclude pseudo root node
    1925                  if node_data is None:
    1926                          return None
    1927
    1928                  # expand/collapse documents on activation
    1929                  if isinstance(node_data, gmDocuments.cDocument):
    1930                          self.Toggle(node)
    1931                          return True
    1932
    1933                  # string nodes are labels such as episodes which may or may not have children
    1934                  if type(node_data) == type('string'):
    1935                          self.Toggle(node)
    1936                          return True
    1937
    1938                  self.__display_part(part = node_data)
    1939                  return True
    1940          #--------------------------------------------------------
    1941 -        def __on_right_click(self, evt):
    1942
    1943                  node = evt.GetItem()
    1944                  self.__curr_node_data = self.GetPyData(node)
    1945
    1946                  # exclude pseudo root node
    1947                  if self.__curr_node_data is None:
    1948                          return None
    1949
    1950                  # documents
    1951                  if isinstance(self.__curr_node_data, gmDocuments.cDocument):
    1952                          self.__handle_doc_context()
    1953
    1954                  # parts
    1955                  if isinstance(self.__curr_node_data, gmDocuments.cDocumentPart):
    1956                          self.__handle_part_context()
    1957
    1958                  del self.__curr_node_data
    1959                  evt.Skip()
    1960          #--------------------------------------------------------
    1961 -        def __activate_as_current_photo(self, evt):
    1962                  self.__curr_node_data.set_as_active_photograph()
    1963          #--------------------------------------------------------
    1964 -        def __display_curr_part(self, evt):
    1965                  self.__display_part(part = self.__curr_node_data)
    1966          #--------------------------------------------------------
    1967 -        def __review_curr_part(self, evt):
    1968                  self.__review_part(part = self.__curr_node_data)
    1969          #--------------------------------------------------------
    1970 -        def __manage_document_descriptions(self, evt):
    1971                  manage_document_descriptions(parent = self, document = self.__curr_node_data)
    1972          #--------------------------------------------------------
    1973 -        def _on_tree_item_gettooltip(self, event):
    1974
    1975                  item = event.GetItem()
    1976
    1977                  if not item.IsOk():
    1978                          event.SetToolTip(u' ')
    1979                          return
    1980
    1981                  data = self.GetPyData(item)
    1982
    1983                  # documents
    1984                  if isinstance(data, gmDocuments.cDocument):
    1985                          tt = data.format()
    1986                  # parts
    1987                  elif isinstance(data, gmDocuments.cDocumentPart):
    1988                          tt = data.format()
    1989                  # other (root, intermediate nodes)
    1990                  else:
    1991                          tt = u' '
    1992
    1993                  event.SetToolTip(tt)
    1994          #--------------------------------------------------------
    1995          # internal API
    1996          #--------------------------------------------------------
    1997 -        def __sort_nodes(self, start_node=None):
    1998
    1999                  if start_node is None:
    2000                          start_node = self.GetRootItem()
    2001
    2002                  # protect against empty tree where not even
    2003                  # a root node exists
    2004                  if not start_node.IsOk():
    2005                          return True
    2006
    2007                  self.SortChildren(start_node)
    2008
    2009                  child_node, cookie = self.GetFirstChild(start_node)
    2010                  while child_node.IsOk():
    2011                          self.__sort_nodes(start_node = child_node)
    2012                          child_node, cookie = self.GetNextChild(start_node, cookie)
    2013
    2014                  return
    2015          #--------------------------------------------------------
    2016 -        def __handle_doc_context(self):
    2017                  self.PopupMenu(self.__doc_context_menu, wx.DefaultPosition)
    2018          #--------------------------------------------------------
    2019 -        def __handle_part_context(self):
    2020                  # make active patient photograph
    2021                  if self.__curr_node_data['type'] == 'patient photograph':
    2022                          ID = wx.NewId()
    2023                          self.__part_context_menu.Append(ID, _('Activate as current photo'))
    2024                          wx.EVT_MENU(self.__part_context_menu, ID, self.__activate_as_current_photo)
    2025                  else:
    2026                          ID = None
    2027
    2028                  self.PopupMenu(self.__part_context_menu, wx.DefaultPosition)
    2029
    2030                  if ID is not None:
    2031                          self.__part_context_menu.Delete(ID)
    2032          #--------------------------------------------------------
    2033          # part level context menu handlers
    2034          #--------------------------------------------------------
    2035 -        def __display_part(self, part):
    2036                  """Display document part."""
    2037
    2038                  # sanity check
    2039                  if part['size'] == 0:
    2040                          _log.debug('cannot display part [%s] - 0 bytes', part['pk_obj'])
    2041                          gmGuiHelpers.gm_show_error (
    2042                                  aMessage = _('Document part does not seem to exist in database !'),
    2043                                  aTitle = _('showing document')
    2044                          )
    2045                          return None
    2046
    2047                  wx.BeginBusyCursor()
    2048
    2049                  cfg = gmCfg.cCfgSQL()
    2050
    2051                  # determine database export chunk size
    2052                  chunksize = int(
    2053                  cfg.get2 (
    2054                          option = "horstspace.blob_export_chunk_size",
    2055                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2056                          bias = 'workplace',
    2057                          default = default_chunksize
    2058                  ))
    2059
    2060                  # shall we force blocking during view ?
    2061                  block_during_view = bool( cfg.get2 (
    2062                          option = 'horstspace.document_viewer.block_during_view',
    2063                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2064                          bias = 'user',
    2065                          default = None
    2066                  ))
    2067
    2068                  # display it
    2069                  successful, msg = part.display_via_mime (
    2070                          chunksize = chunksize,
    2071                          block = block_during_view
    2072                  )
    2073
    2074                  wx.EndBusyCursor()
    2075
    2076                  if not successful:
    2077                          gmGuiHelpers.gm_show_error (
    2078                                  aMessage = _('Cannot display document part:\n%s') % msg,
    2079                                  aTitle = _('showing document')
    2080                          )
    2081                          return None
    2082
    2083                  # handle review after display
    2084                  # 0: never
    2085                  # 1: always
    2086                  # 2: if no review by myself exists yet
    2087                  # 3: if no review at all exists yet
    2088                  # 4: if no review by responsible reviewer
    2089                  review_after_display = int(cfg.get2 (
    2090                          option = 'horstspace.document_viewer.review_after_display',
    2091                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2092                          bias = 'user',
    2093                          default = 3
    2094                  ))
    2095                  if review_after_display == 1:                   # always review
    2096                          self.__review_part(part=part)
    2097                  elif review_after_display == 2:                 # review if no review by me exists
    2098                          review_by_me = filter(lambda rev: rev['is_your_review'], part.get_reviews())
    2099                          if len(review_by_me) == 0:
    2100                                  self.__review_part(part = part)
    2101                  elif review_after_display == 3:
    2102                          if len(part.get_reviews()) == 0:
    2103                                  self.__review_part(part = part)
    2104                  elif review_after_display == 4:
    2105                          reviewed_by_responsible = filter(lambda rev: rev['is_review_by_responsible_reviewer'], part.get_reviews())
    2106                          if len(reviewed_by_responsible) == 0:
    2107                                  self.__review_part(part = part)
    2108
    2109                  return True
    2110          #--------------------------------------------------------
    2111 -        def __review_part(self, part=None):
    2112                  dlg = cReviewDocPartDlg (
    2113                          parent = self,
    2114                          id = -1,
    2115                          part = part
    2116                  )
    2117                  dlg.ShowModal()
    2118                  dlg.Destroy()
    2119          #--------------------------------------------------------
    2120 -        def __move_part(self, evt):
    2121                  target_doc = manage_documents (
    2122                          parent = self,
    2123                          msg = _('\nSelect the document into which to move the selected part !\n')
    2124                  )
    2125                  if target_doc is None:
    2126                          return
    2127                  self.__curr_node_data['pk_doc'] = target_doc['pk_doc']
    2128                  self.__curr_node_data.save()
    2129          #--------------------------------------------------------
    2130 -        def __delete_part(self, evt):
    2131                  delete_it = gmGuiHelpers.gm_show_question (
    2132                          cancel_button = True,
    2133                          title = _('Deleting document part'),
    2134                          question = _(
    2135                                  'Are you sure you want to delete the %s part #%s\n'
    2136                                  '\n'
    2137                                  '%s'
    2138                                  'from the following document\n'
    2139                                  '\n'
    2140                                  ' %s (%s)\n'
    2141                                  '%s'
    2142                                  '\n'
    2143                                  'Really delete ?\n'
    2144                                  '\n'
    2145                                  '(this action cannot be reversed)'
    2146                          ) % (
    2147                                  gmTools.size2str(self.__curr_node_data['size']),
    2148                                  self.__curr_node_data['seq_idx'],
    2149                                  gmTools.coalesce(self.__curr_node_data['obj_comment'], u'', u' "%s"\n\n'),
    2150                                  self.__curr_node_data['l10n_type'],
    2151                                  gmDateTime.pydt_strftime(self.__curr_node_data['date_generated'], format = '%Y-%m-%d', accuracy = gmDateTime.acc_days),
    2152                                  gmTools.coalesce(self.__curr_node_data['doc_comment'], u'', u' "%s"\n')
    2153                          )
    2154                  )
    2155                  if not delete_it:
    2156                          return
    2157
    2158                  gmDocuments.delete_document_part (
    2159                          part_pk = self.__curr_node_data['pk_obj'],
    2160                          encounter_pk = gmPerson.gmCurrentPatient().emr.active_encounter['pk_encounter']
    2161                  )
    2162          #--------------------------------------------------------
    2163 -        def __process_part(self, action=None, l10n_action=None):
    2164
    2165                  gmHooks.run_hook_script(hook = u'before_%s_doc_part' % action)
    2166
    2167                  wx.BeginBusyCursor()
    2168
    2169                  # detect wrapper
    2170                  found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action)
    2171                  if not found:
    2172                          found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action)
    2173                  if not found:
    2174                          _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action)
    2175                          wx.EndBusyCursor()
    2176                          gmGuiHelpers.gm_show_error (
    2177                                  _('Cannot %(l10n_action)s document part - %(l10n_action)s command not found.\n'
    2178                                    '\n'
    2179                                    'Either of gm-%(action)s_doc or gm-%(action)s_doc.bat\n'
    2180                                    'must be in the execution path. The command will\n'
    2181                                    'be passed the filename to %(l10n_action)s.'
    2182                                  ) % {'action': action, 'l10n_action': l10n_action},
    2183                                  _('Processing document part: %s') % l10n_action
    2184                          )
    2185                          return
    2186
    2187                  cfg = gmCfg.cCfgSQL()
    2188
    2189                  # determine database export chunk size
    2190                  chunksize = int(cfg.get2 (
    2191                          option = "horstspace.blob_export_chunk_size",
    2192                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2193                          bias = 'workplace',
    2194                          default = default_chunksize
    2195                  ))
    2196
    2197                  part_file = self.__curr_node_data.export_to_file(aChunkSize = chunksize)
    2198
    2199                  cmd = u'%s %s' % (external_cmd, part_file)
    2200                  if os.name == 'nt':
    2201                          blocking = True
    2202                  else:
    2203                          blocking = False
    2204                  success = gmShellAPI.run_command_in_shell (
    2205                          command = cmd,
    2206                          blocking = blocking
    2207                  )
    2208
    2209                  wx.EndBusyCursor()
    2210
    2211                  if not success:
    2212                          _log.error('%s command failed: [%s]', action, cmd)
    2213                          gmGuiHelpers.gm_show_error (
    2214                                  _('Cannot %(l10n_action)s document part - %(l10n_action)s command failed.\n'
    2215                                    '\n'
    2216                                    'You may need to check and fix either of\n'
    2217                                    ' gm-%(action)s_doc (Unix/Mac) or\n'
    2218                                    ' gm-%(action)s_doc.bat (Windows)\n'
    2219                                    '\n'
    2220                                    'The command is passed the filename to %(l10n_action)s.'
    2221                                  ) % {'action': action, 'l10n_action': l10n_action},
    2222                                  _('Processing document part: %s') % l10n_action
    2223                          )
    2224                  else:
    2225                          if action == 'mail':
    2226                                  curr_pat = gmPerson.gmCurrentPatient()
    2227                                  emr = curr_pat.emr
    2228                                  emr.add_clin_narrative (
    2229                                          soap_cat = None,
    2230                                          note =  _('document part handed over to email program: %s') % self.__curr_node_data.format(single_line = True),
    2231                                          episode = self.__curr_node_data['pk_episode']
    2232                                  )
    2233          #--------------------------------------------------------
    2234 -        def __print_part(self, evt):
    2235                  self.__process_part(action = u'print', l10n_action = _('print'))
    2236          #--------------------------------------------------------
    2237 -        def __fax_part(self, evt):
    2238                  self.__process_part(action = u'fax', l10n_action = _('fax'))
    2239          #--------------------------------------------------------
    2240 -        def __mail_part(self, evt):
    2241                  self.__process_part(action = u'mail', l10n_action = _('mail'))
    2242          #--------------------------------------------------------
    2243          # document level context menu handlers
    2244          #--------------------------------------------------------
    2245 -        def __select_encounter(self, evt):
    2246                  enc = gmEncounterWidgets.select_encounters (
    2247                          parent = self,
    2248                          patient = gmPerson.gmCurrentPatient()
    2249                  )
    2250                  if not enc:
    2251                          return
    2252                  self.__curr_node_data['pk_encounter'] = enc['pk_encounter']
    2253                  self.__curr_node_data.save()
    2254          #--------------------------------------------------------
    2255 -        def __edit_encounter_details(self, evt):
    2256                  enc = gmEMRStructItems.cEncounter(aPK_obj = self.__curr_node_data['pk_encounter'])
    2257                  gmEncounterWidgets.edit_encounter(parent = self, encounter = enc)
    2258          #--------------------------------------------------------
    2259 -        def __process_doc(self, action=None, l10n_action=None):
    2260
    2261                  gmHooks.run_hook_script(hook = u'before_%s_doc' % action)
    2262
    2263                  wx.BeginBusyCursor()
    2264
    2265                  # detect wrapper
    2266                  found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc' % action)
    2267                  if not found:
    2268                          found, external_cmd = gmShellAPI.detect_external_binary(u'gm-%s_doc.bat' % action)
    2269                  if not found:
    2270                          _log.error('neither of gm-%s_doc or gm-%s_doc.bat found', action, action)
    2271                          wx.EndBusyCursor()
    2272                          gmGuiHelpers.gm_show_error (
    2273                                  _('Cannot %(l10n_action)s document - %(l10n_action)s command not found.\n'
    2274                                    '\n'
    2275                                    'Either of gm-%(action)s_doc or gm-%(action)s_doc.bat\n'
    2276                                    'must be in the execution path. The command will\n'
    2277                                    'be passed a list of filenames to %(l10n_action)s.'
    2278                                  ) % {'action': action, 'l10n_action': l10n_action},
    2279                                  _('Processing document: %s') % l10n_action
    2280                          )
    2281                          return
    2282
    2283                  cfg = gmCfg.cCfgSQL()
    2284
    2285                  # determine database export chunk size
    2286                  chunksize = int(cfg.get2 (
    2287                          option = "horstspace.blob_export_chunk_size",
    2288                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2289                          bias = 'workplace',
    2290                          default = default_chunksize
    2291                  ))
    2292
    2293                  part_files = self.__curr_node_data.export_parts_to_files(chunksize = chunksize)
    2294
    2295                  if os.name == 'nt':
    2296                          blocking = True
    2297                  else:
    2298                          blocking = False
    2299                  cmd = external_cmd + u' ' + u' '.join(part_files)
    2300                  success = gmShellAPI.run_command_in_shell (
    2301                          command = cmd,
    2302                          blocking = blocking
    2303                  )
    2304
    2305                  wx.EndBusyCursor()
    2306
    2307                  if not success:
    2308                          _log.error('%s command failed: [%s]', action, cmd)
    2309                          gmGuiHelpers.gm_show_error (
    2310                                  _('Cannot %(l10n_action)s document - %(l10n_action)s command failed.\n'
    2311                                    '\n'
    2312                                    'You may need to check and fix either of\n'
    2313                                    ' gm-%(action)s_doc (Unix/Mac) or\n'
    2314                                    ' gm-%(action)s_doc.bat (Windows)\n'
    2315                                    '\n'
    2316                                    'The command is passed a list of filenames to %(l10n_action)s.'
    2317                                  ) % {'action': action, 'l10n_action': l10n_action},
    2318                                  _('Processing document: %s') % l10n_action
    2319                          )
    2320          #--------------------------------------------------------
    2321          # FIXME: icons in the plugin toolbar
    2322 -        def __print_doc(self, evt):
    2323                  self.__process_doc(action = u'print', l10n_action = _('print'))
    2324          #--------------------------------------------------------
    2325 -        def __fax_doc(self, evt):
    2326                  self.__process_doc(action = u'fax', l10n_action = _('fax'))
    2327          #--------------------------------------------------------
    2328 -        def __mail_doc(self, evt):
    2329                  self.__process_doc(action = u'mail', l10n_action = _('mail'))
    2330          #--------------------------------------------------------
    2331 -        def __add_part(self, evt):
    2332                  dlg = wx.FileDialog (
    2333                          parent = self,
    2334                          message = _('Choose a file'),
    2335                          defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')),
    2336                          defaultFile = '',
    2337                          wildcard = "%s (*)|*|PNGs (*.png)|*.png|PDFs (*.pdf)|*.pdf|TIFFs (*.tif)|*.tif|JPEGs (*.jpg)|*.jpg|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')),
    2338                          style = wx.OPEN | wx.FILE_MUST_EXIST | wx.MULTIPLE
    2339                  )
    2340                  result = dlg.ShowModal()
    2341                  if result != wx.ID_CANCEL:
    2342                          self.__curr_node_data.add_parts_from_files(files = dlg.GetPaths(), reviewer = gmStaff.gmCurrentProvider()['pk_staff'])
    2343                  dlg.Destroy()
    2344          #--------------------------------------------------------
    2345 -        def __access_external_original(self, evt):
    2346
    2347                  gmHooks.run_hook_script(hook = u'before_external_doc_access')
    2348
    2349                  wx.BeginBusyCursor()
    2350
    2351                  # detect wrapper
    2352                  found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.sh')
    2353                  if not found:
    2354                          found, external_cmd = gmShellAPI.detect_external_binary(u'gm_access_external_doc.bat')
    2355                  if not found:
    2356                          _log.error('neither of gm_access_external_doc.sh or .bat found')
    2357                          wx.EndBusyCursor()
    2358                          gmGuiHelpers.gm_show_error (
    2359                                  _('Cannot access external document - access command not found.\n'
    2360                                    '\n'
    2361                                    'Either of gm_access_external_doc.sh or *.bat must be\n'
    2362                                    'in the execution path. The command will be passed the\n'
    2363                                    'document type and the reference URL for processing.'
    2364                                  ),
    2365                                  _('Accessing external document')
    2366                          )
    2367                          return
    2368
    2369                  cmd = u'%s "%s" "%s"' % (external_cmd, self.__curr_node_data['type'], self.__curr_node_data['ext_ref'])
    2370                  if os.name == 'nt':
    2371                          blocking = True
    2372                  else:
    2373                          blocking = False
    2374                  success = gmShellAPI.run_command_in_shell (
    2375                          command = cmd,
    2376                          blocking = blocking
    2377                  )
    2378
    2379                  wx.EndBusyCursor()
    2380
    2381                  if not success:
    2382                          _log.error('External access command failed: [%s]', cmd)
    2383                          gmGuiHelpers.gm_show_error (
    2384                                  _('Cannot access external document - access command failed.\n'
    2385                                    '\n'
    2386                                    'You may need to check and fix either of\n'
    2387                                    ' gm_access_external_doc.sh (Unix/Mac) or\n'
    2388                                    ' gm_access_external_doc.bat (Windows)\n'
    2389                                    '\n'
    2390                                    'The command is passed the document type and the\n'
    2391                                    'external reference URL on the command line.'
    2392                                  ),
    2393                                  _('Accessing external document')
    2394                          )
    2395          #--------------------------------------------------------
    2396 -        def __export_doc_to_disk(self, evt):
    2397                  """Export document into directory.
    2398
    2399                  - one file per object
    2400                  - into subdirectory named after patient
    2401                  """
    2402                  pat = gmPerson.gmCurrentPatient()
    2403                  dname = '%s-%s%s' % (
    2404                          self.__curr_node_data['l10n_type'],
    2405                          self.__curr_node_data['clin_when'].strftime('%Y-%m-%d'),
    2406                          gmTools.coalesce(self.__curr_node_data['ext_ref'], '', '-%s').replace(' ', '_')
    2407                  )
    2408                  def_dir = os.path.expanduser(os.path.join('~', 'gnumed', pat['dirname'], dname))
    2409                  gmTools.mkdir(def_dir)
    2410
    2411                  dlg = wx.DirDialog (
    2412                          parent = self,
    2413                          message = _('Save document into directory ...'),
    2414                          defaultPath = def_dir,
    2415                          style = wx.DD_DEFAULT_STYLE
    2416                  )
    2417                  result = dlg.ShowModal()
    2418                  dirname = dlg.GetPath()
    2419                  dlg.Destroy()
    2420
    2421                  if result != wx.ID_OK:
    2422                          return True
    2423
    2424                  wx.BeginBusyCursor()
    2425
    2426                  cfg = gmCfg.cCfgSQL()
    2427
    2428                  # determine database export chunk size
    2429                  chunksize = int(cfg.get2 (
    2430                          option = "horstspace.blob_export_chunk_size",
    2431                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2432                          bias = 'workplace',
    2433                          default = default_chunksize
    2434                  ))
    2435
    2436                  fnames = self.__curr_node_data.export_parts_to_files(export_dir = dirname, chunksize = chunksize)
    2437
    2438                  wx.EndBusyCursor()
    2439
    2440                  gmDispatcher.send(signal='statustext', msg=_('Successfully exported %s parts into the directory [%s].') % (len(fnames), dirname))
    2441
    2442                  return True
    2443          #--------------------------------------------------------
    2444 -        def __delete_document(self, evt):
    2445                  result = gmGuiHelpers.gm_show_question (
    2446                          aMessage = _('Are you sure you want to delete the document ?'),
    2447                          aTitle = _('Deleting document')
    2448                  )
    2449                  if result is True:
    2450                          curr_pat = gmPerson.gmCurrentPatient()
    2451                          emr = curr_pat.get_emr()
    2452                          enc = emr.active_encounter
    2453                          gmDocuments.delete_document(document_id = self.__curr_node_data['pk_doc'], encounter_id = enc['pk_encounter'])
    2454  #============================================================
    2455  # main
    2456  #------------------------------------------------------------
    2457  if __name__ == '__main__':
    2458
    2459          gmI18N.activate_locale()
    2460          gmI18N.install_domain(domain = 'gnumed')
    2461
    2462          #----------------------------------------
    2463          #----------------------------------------
    2464          if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
    2465  #               test_*()
    2466                  pass
    2467
    2468  #============================================================
    2469