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