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