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

# Source Code for Module Gnumed.wxpython.gmGuiMain

       1  # -*- coding: utf8 -*-
       2  """GNUmed GUI client.
       3
       4  This contains the GUI application framework and main window
       5  of the all signing all dancing GNUmed Python Reference
       6  client. It relies on the <gnumed.py> launcher having set up
       7  the non-GUI-related runtime environment.
       8
       9  copyright: authors
      10  """
      11  #==============================================================================
      12  __author__  = "H. Herb <hherb@gnumed.net>,\
      13                             K. Hilbert <Karsten.Hilbert@gmx.net>,\
      14                             I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
      15  __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
      16
      17  # stdlib
      18  import sys
      19  import time
      20  import os
      21  import os.path
      22  import datetime as pyDT
      23  import shutil
      24  import logging
      25  import urllib2
      26  import subprocess
      27  import glob
      28
      29
      30  # 3rd party libs
      31  # wxpython version cannot be enforced inside py2exe and friends
      32  if not hasattr(sys, 'frozen'):
      33          import wxversion
      34          wxversion.ensureMinimal('2.8-unicode', optionsRequired=True)
      35
      36  try:
      37          import wx
      38  except ImportError:
      39          print "GNUmed startup: Cannot import wxPython library."
      40          print "GNUmed startup: Make sure wxPython is installed."
      41          print 'CRITICAL ERROR: Error importing wxPython. Halted.'
      42          raise
      43
      44  # do this check just in case, so we can make sure
      45  # py2exe and friends include the proper version, too
      46  version = int(u'%s%s' % (wx.MAJOR_VERSION, wx.MINOR_VERSION))
      47  if (version < 28) or ('unicode' not in wx.PlatformInfo):
      48          print "GNUmed startup: Unsupported wxPython version (%s: %s)." % (wx.VERSION_STRING, wx.PlatformInfo)
      49          print "GNUmed startup: wxPython 2.8+ with unicode support is required."
      50          print 'CRITICAL ERROR: Proper wxPython version not found. Halted.'
      51          raise ValueError('wxPython 2.8+ with unicode support not found')
      52
      53
      54  # GNUmed libs
      55  from Gnumed.pycommon import gmCfg
      56  from Gnumed.pycommon import gmPG2
      57  from Gnumed.pycommon import gmDispatcher
      58  from Gnumed.pycommon import gmGuiBroker
      59  from Gnumed.pycommon import gmI18N
      60  from Gnumed.pycommon import gmExceptions
      61  from Gnumed.pycommon import gmShellAPI
      62  from Gnumed.pycommon import gmTools
      63  from Gnumed.pycommon import gmDateTime
      64  from Gnumed.pycommon import gmHooks
      65  from Gnumed.pycommon import gmBackendListener
      66  from Gnumed.pycommon import gmCfg2
      67  from Gnumed.pycommon import gmLog2
      68  from Gnumed.pycommon import gmNetworkTools
      69
      70  from Gnumed.business import gmPerson
      71  from Gnumed.business import gmClinicalRecord
      72  from Gnumed.business import gmPraxis
      73  from Gnumed.business import gmEMRStructItems
      74  from Gnumed.business import gmVaccination
      75  from Gnumed.business import gmArriba
      76  from Gnumed.business import gmStaff
      77
      78  from Gnumed.exporters import gmPatientExporter
      79
      80  from Gnumed.wxpython import gmGuiHelpers
      81  from Gnumed.wxpython import gmHorstSpace
      82  from Gnumed.wxpython import gmEMRBrowser
      83  from Gnumed.wxpython import gmDemographicsWidgets
      84  from Gnumed.wxpython import gmPersonCreationWidgets
      85  from Gnumed.wxpython import gmEMRStructWidgets
      86  from Gnumed.wxpython import gmPatSearchWidgets
      87  from Gnumed.wxpython import gmAllergyWidgets
      88  from Gnumed.wxpython import gmListWidgets
      89  from Gnumed.wxpython import gmProviderInboxWidgets
      90  from Gnumed.wxpython import gmCfgWidgets
      91  from Gnumed.wxpython import gmExceptionHandlingWidgets
      92  from Gnumed.wxpython import gmNarrativeWidgets
      93  from Gnumed.wxpython import gmPhraseWheel
      94  from Gnumed.wxpython import gmMedicationWidgets
      95  from Gnumed.wxpython import gmStaffWidgets
      96  from Gnumed.wxpython import gmDocumentWidgets
      97  from Gnumed.wxpython import gmTimer
      98  from Gnumed.wxpython import gmMeasurementWidgets
      99  from Gnumed.wxpython import gmFormWidgets
     100  from Gnumed.wxpython import gmSnellen
     101  from Gnumed.wxpython import gmVaccWidgets
     102  from Gnumed.wxpython import gmPersonContactWidgets
     103  from Gnumed.wxpython import gmI18nWidgets
     104  from Gnumed.wxpython import gmCodingWidgets
     105  from Gnumed.wxpython import gmOrganizationWidgets
     106  from Gnumed.wxpython import gmAuthWidgets
     107  from Gnumed.wxpython import gmFamilyHistoryWidgets
     108  from Gnumed.wxpython import gmDataPackWidgets
     109  from Gnumed.wxpython import gmContactWidgets
     110  from Gnumed.wxpython import gmAddressWidgets
     111  from Gnumed.wxpython import gmBillingWidgets
     112  from Gnumed.wxpython import gmKeywordExpansionWidgets
     113  from Gnumed.wxpython import gmAccessPermissionWidgets
     114  from Gnumed.wxpython import gmPraxisWidgets
     115
     116
     117  try:
     118          _('dummy-no-need-to-translate-but-make-epydoc-happy')
     119  except NameError:
     120          _ = lambda x:x
     121
     122  _cfg = gmCfg2.gmCfgData()
     123  _provider = None
     124  _scripting_listener = None
     125  _original_wxEndBusyCursor = None
     126
     127  _log = logging.getLogger('gm.main')
     128  _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
     129
     130  #==============================================================================
     131 -class gmTopLevelFrame(wx.Frame):
     132          """GNUmed client's main windows frame.
     133
     134          This is where it all happens. Avoid popping up any other windows.
     135          Most user interaction should happen to and from widgets within this frame
     136          """
     137          #----------------------------------------------
     138 -        def __init__(self, parent, id, title, size=wx.DefaultSize):
     139                  """You'll have to browse the source to understand what the constructor does
     140                  """
     141                  wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
     142
     143                  self.__setup_font()
     144
     145                  self.__gb = gmGuiBroker.GuiBroker()
     146                  self.__pre_exit_callbacks = []
     147                  self.bar_width = -1
     148                  self.menu_id2plugin = {}
     149
     150                  _log.info('workplace is >>>%s<<<', gmPraxis.gmCurrentPraxisBranch().active_workplace)
     151
     152                  self.__setup_main_menu()
     153                  self.setup_statusbar()
     154                  self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
     155                          gmTools.coalesce(_provider['title'], ''),
     156                          _provider['firstnames'][:1],
     157                          _provider['lastnames'],
     158                          _provider['short_alias'],
     159                          _provider['db_user']
     160                  ))
     161
     162                  self.__set_window_title_template()
     163                  self.__update_window_title()
     164
     165                  #icon_bundle = wx.IconBundle()
     166                  #icon_bundle.AddIcon(wx.Icon("my_icon_16_16.ico", wx.BITMAP_TYPE_ICO))
     167                  #icon_bundle.AddIcon(wx.Icon("my_icon_32_32.ico", wx.BITMAP_TYPE_ICO))
     168                  #self.SetIcons(icon_bundle)
     169                  self.SetIcon(gmTools.get_icon(wx = wx))
     170
     171                  self.__register_events()
     172
     173                  self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
     174                  self.vbox = wx.BoxSizer(wx.VERTICAL)
     175                  self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
     176
     177                  self.SetAutoLayout(True)
     178                  self.SetSizerAndFit(self.vbox)
     179
     180                  # don't allow the window to get too small
     181                  # setsizehints only allows minimum size, therefore window can't become small enough
     182                  # effectively we need the font size to be configurable according to screen size
     183                  #self.vbox.SetSizeHints(self)
     184                  self.__set_GUI_size()
     185
     186          #----------------------------------------------
     187 -        def __setup_font(self):
     188
     189                  font = self.GetFont()
     190                  _log.debug('system default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
     191
     192                  desired_font_face = _cfg.get (
     193                          group = u'workplace',
     194                          option = u'client font',
     195                          source_order = [
     196                                  ('explicit', 'return'),
     197                                  ('workbase', 'return'),
     198                                  ('local', 'return'),
     199                                  ('user', 'return'),
     200                                  ('system', 'return')
     201                          ]
     202                  )
     203
     204                  fonts2try = []
     205                  if desired_font_face is not None:
     206                          _log.info('client is configured to use font [%s]', desired_font_face)
     207                          fonts2try.append(desired_font_face)
     208
     209                  if wx.Platform == '__WXMSW__':
     210                          sane_font_face = u'DejaVu Sans'
     211                          _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face)
     212                          fonts2try.append(sane_font_face)
     213
     214                  if len(fonts2try) == 0:
     215                          return
     216
     217                  for font_face in fonts2try:
     218                          success = font.SetFaceName(font_face)
     219                          if success:
     220                                  self.SetFont(font)
     221                                  _log.debug('switched font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
     222                                  return
     223                          font = self.GetFont()
     224                          _log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face)
     225
     226                  return
     227          #----------------------------------------------
     228 -        def __set_GUI_size(self):
     229                  """Try to get previous window size from backend."""
     230
     231                  cfg = gmCfg.cCfgSQL()
     232
     233                  # width
     234                  width = int(cfg.get2 (
     235                          option = 'main.window.width',
     236                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     237                          bias = 'workplace',
     238                          default = 800
     239                  ))
     240
     241                  # height
     242                  height = int(cfg.get2 (
     243                          option = 'main.window.height',
     244                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
     245                          bias = 'workplace',
     246                          default = 600
     247                  ))
     248
     249                  dw = wx.DisplaySize()[0]
     250                  dh = wx.DisplaySize()[1]
     251
     252                  _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
     253                  _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
     254                  _log.debug('previous GUI size [%s:%s]', width, height)
     255
     256                  # max size
     257                  if width > dw:
     258                          _log.debug('adjusting GUI width from %s to %s', width, dw)
     259                          width = dw
     260
     261                  if height > dh:
     262                          _log.debug('adjusting GUI height from %s to %s', height, dh)
     263                          height = dh
     264
     265                  # min size
     266                  if width < 100:
     267                          _log.debug('adjusting GUI width to minimum of 100 pixel')
     268                          width = 100
     269                  if height < 100:
     270                          _log.debug('adjusting GUI height to minimum of 100 pixel')
     271                          height = 100
     272
     273                  _log.info('setting GUI to size [%s:%s]', width, height)
     274
     275                  self.SetClientSize(wx.Size(width, height))
     276          #----------------------------------------------
     912          #----------------------------------------------
     913 -        def __load_plugins(self):
     914                  pass
     915          #----------------------------------------------
     916          # event handling
     917          #----------------------------------------------
     918 -        def __register_events(self):
     919                  """register events we want to react to"""
     920
     921                  wx.EVT_CLOSE(self, self.OnClose)
     922                  wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
     923                  wx.EVT_END_SESSION(self, self._on_end_session)
     924
     925                  gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
     926                  gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
     927                  gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
     928                  gmDispatcher.connect(signal = u'praxis_branch_mod_db', receiver = self._on_pat_name_changed)
     929                  gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
     930                  gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
     931                  gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
     932                  gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
     933                  gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
     934
     935                  gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
     936          #----------------------------------------------
     937 -        def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
     938
     939                  _log.debug('registering plugin with menu system')
     940                  _log.debug(' generic name: %s', plugin_name)
     941                  _log.debug(' class name: %s', class_name)
     942                  _log.debug(' specific menu: %s', menu_name)
     943                  _log.debug(' menu item: %s', menu_item_name)
     944
     945                  # add to generic "go to plugin" menu
     946                  item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
     947                  self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
     948                  self.menu_id2plugin[item.Id] = class_name
     949
     950                  # add to specific menu if so requested
     951                  if menu_name is not None:
     952                          menu = self.__gb['main.%smenu' % menu_name]
     953                          item = menu.Append(-1, menu_item_name, menu_help_string)
     954                          self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
     955                          self.menu_id2plugin[item.Id] = class_name
     956
     957                  return True
     958          #----------------------------------------------
     959 -        def __on_raise_a_plugin(self, evt):
     960                  gmDispatcher.send (
     961                          signal = u'display_widget',
     962                          name = self.menu_id2plugin[evt.Id]
     963                  )
     964          #----------------------------------------------
     965 -        def _on_query_end_session(self, *args, **kwargs):
     966                  wx.Bell()
     967                  wx.Bell()
     968                  wx.Bell()
     969                  _log.warning('unhandled event detected: QUERY_END_SESSION')
     970                  _log.info('we should be saving ourselves from here')
     971                  gmLog2.flush()
     972                  print "unhandled event detected: QUERY_END_SESSION"
     973          #----------------------------------------------
     974 -        def _on_end_session(self, *args, **kwargs):
     975                  wx.Bell()
     976                  wx.Bell()
     977                  wx.Bell()
     978                  _log.warning('unhandled event detected: END_SESSION')
     979                  gmLog2.flush()
     980                  print "unhandled event detected: END_SESSION"
     981          #-----------------------------------------------
     982 -        def _register_pre_exit_callback(self, callback=None):
     983                  if not callable(callback):
     984                          raise TypeError(u'callback [%s] not callable' % callback)
     985
     986                  self.__pre_exit_callbacks.append(callback)
     987          #-----------------------------------------------
     988 -        def _on_set_statustext_pubsub(self, context=None):
     989                  msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
     990                  wx.CallAfter(self.SetStatusText, msg)
     991
     992                  try:
     993                          if context.data['beep']:
     994                                  wx.Bell()
     995                  except KeyError:
     996                          pass
     997          #-----------------------------------------------
     998 -        def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
     999
    1000                  if msg is None:
    1001                          msg = _('programmer forgot to specify status message')
    1002
    1003                  if loglevel is not None:
    1004                          _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
    1005
    1006                  msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
    1007                  wx.CallAfter(self.SetStatusText, msg)
    1008
    1009                  if beep:
    1010                          wx.Bell()
    1011          #-----------------------------------------------
    1012 -        def _on_db_maintenance_warning(self):
    1013                  wx.CallAfter(self.__on_db_maintenance_warning)
    1014          #-----------------------------------------------
    1015 -        def __on_db_maintenance_warning(self):
    1016
    1017                  self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
    1018                  wx.Bell()
    1019                  if not wx.GetApp().IsActive():
    1020                          self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
    1021
    1022                  gmHooks.run_hook_script(hook = u'db_maintenance_warning')
    1023
    1024                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
    1025                          None,
    1026                          -1,
    1027                          caption = _('Database shutdown warning'),
    1028                          question = _(
    1029                                  'The database will be shut down for maintenance\n'
    1030                                  'in a few minutes.\n'
    1031                                  '\n'
    1032                                  'In order to not suffer any loss of data you\n'
    1033                                  'will need to save your current work and log\n'
    1034                                  'out of this GNUmed client.\n'
    1035                          ),
    1036                          button_defs = [
    1037                                  {
    1038                                          u'label': _('Close now'),
    1039                                          u'tooltip': _('Close this GNUmed client immediately.'),
    1040                                          u'default': False
    1041                                  },
    1042                                  {
    1043                                          u'label': _('Finish work'),
    1044                                          u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
    1045                                          u'default': True
    1046                                  }
    1047                          ]
    1048                  )
    1049                  decision = dlg.ShowModal()
    1050                  if decision == wx.ID_YES:
    1051                          top_win = wx.GetApp().GetTopWindow()
    1052                          wx.CallAfter(top_win.Close)
    1053          #-----------------------------------------------
    1054 -        def _on_request_user_attention(self, msg=None, urgent=False):
    1055                  wx.CallAfter(self.__on_request_user_attention, msg, urgent)
    1056          #-----------------------------------------------
    1057 -        def __on_request_user_attention(self, msg=None, urgent=False):
    1058                  # already in the foreground ?
    1059                  if not wx.GetApp().IsActive():
    1060                          if urgent:
    1061                                  self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
    1062                          else:
    1063                                  self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
    1064
    1065                  if msg is not None:
    1066                          self.SetStatusText(msg)
    1067
    1068                  if urgent:
    1069                          wx.Bell()
    1070
    1071                  gmHooks.run_hook_script(hook = u'request_user_attention')
    1072          #-----------------------------------------------
    1073 -        def _on_pat_name_changed(self):
    1074                  wx.CallAfter(self.__on_pat_name_changed)
    1075          #-----------------------------------------------
    1076 -        def __on_pat_name_changed(self):
    1077                  self.__update_window_title()
    1078          #-----------------------------------------------
    1079 -        def _on_post_patient_selection(self, **kwargs):
    1080                  wx.CallAfter(self.__on_post_patient_selection, **kwargs)
    1081          #----------------------------------------------
    1082 -        def __on_post_patient_selection(self, **kwargs):
    1083                  self.__update_window_title()
    1084                  gmDispatcher.send(signal = 'statustext', msg = u'')
    1085                  try:
    1086                          gmHooks.run_hook_script(hook = u'post_patient_activation')
    1087                  except:
    1088                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
    1089                          raise
    1090          #----------------------------------------------
    1091 -        def _pre_selection_callback(self):
    1092                  return self.__sanity_check_encounter()
    1093          #----------------------------------------------
    1094 -        def __sanity_check_encounter(self):
    1095
    1096                  # FIXME: should consult a centralized security provider
    1097                  # secretaries cannot edit encounters
    1098                  if _provider['role'] == u'secretary':
    1099                          return True
    1100
    1101                  dbcfg = gmCfg.cCfgSQL()
    1102                  check_enc = bool(dbcfg.get2 (
    1103                          option = 'encounter.show_editor_before_patient_change',
    1104                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1105                          bias = 'user',
    1106                          default = True                                  # True: if needed, not always unconditionally
    1107                  ))
    1108
    1109                  if not check_enc:
    1110                          return True
    1111
    1112                  pat = gmPerson.gmCurrentPatient()
    1113                  emr = pat.get_emr()
    1114                  enc = emr.active_encounter
    1115
    1116                  # did we add anything to the EMR ?
    1117                  has_narr = enc.has_narrative()
    1118                  has_docs = enc.has_documents()
    1119
    1120                  if (not has_narr) and (not has_docs):
    1121                          return True
    1122
    1123                  empty_aoe = (gmTools.coalesce(enc['assessment_of_encounter'], '').strip() == u'')
    1124                  zero_duration = (enc['last_affirmed'] == enc['started'])
    1125
    1126                  # all is well anyway
    1127                  if (not empty_aoe) and (not zero_duration):
    1128                          return True
    1129
    1130                  if zero_duration:
    1131                          enc['last_affirmed'] = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
    1132
    1133                  # no narrative, presumably only import of docs and done
    1134                  if not has_narr:
    1135                          if empty_aoe:
    1136                                  enc['assessment_of_encounter'] = _('only documents added')
    1137                          enc['pk_type'] = gmEMRStructItems.get_encounter_type(description = 'chart review')[0]['pk']
    1138                          # "last_affirmed" should be latest modified_at of relevant docs but that's a lot more involved
    1139                          enc.save_payload()
    1140                          return True
    1141
    1142                  # does have narrative
    1143                  if empty_aoe:
    1144                          # - work out suitable default
    1145                          epis = emr.get_episodes_by_encounter()
    1146                          if len(epis) > 0:
    1147                                  enc_summary = ''
    1148                                  for epi in epis:
    1149                                          enc_summary += '%s; ' % epi['description']
    1150                                  enc['assessment_of_encounter'] = enc_summary
    1151
    1152                  msg = _('Edit the current encounter of the patient you are ABOUT TO LEAVE:')
    1153                  gmEMRStructWidgets.edit_encounter(parent = self, encounter = enc, msg = msg)
    1154
    1155                  return True
    1156          #----------------------------------------------
    1157          # menu "paperwork"
    1158          #----------------------------------------------
    1159 -        def __on_show_docs(self, evt):
    1160                  gmDispatcher.send(signal='show_document_viewer')
    1161          #----------------------------------------------
    1162 -        def __on_new_letter(self, evt):
    1163                  pat = gmPerson.gmCurrentPatient()
    1164                  if not pat.connected:
    1165                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot write letter. No active patient.'), beep = True)
    1166                          return True
    1167                  gmFormWidgets.print_doc_from_template(parent = self, keep_a_copy = True)
    1168          #----------------------------------------------
    1169 -        def __on_show_placeholders(self, evt):
    1170                  from Gnumed.wxpython.gmMacro import show_placeholders
    1171                  show_placeholders()
    1172          #----------------------------------------------
    1173          # help menu
    1174          #----------------------------------------------
    1175 -        def OnAbout(self, event):
    1176                  from Gnumed.wxpython import gmAbout
    1177                  gmAbout = gmAbout.AboutFrame (
    1178                          self,
    1179                          -1,
    1180                          _("About GNUmed"),
    1181                          size=wx.Size(350, 300),
    1182                          style = wx.MAXIMIZE_BOX,
    1183                          version = _cfg.get(option = 'client_version'),
    1184                          debug = _cfg.get(option = 'debug')
    1185                  )
    1186                  gmAbout.Centre(wx.BOTH)
    1187                  gmTopLevelFrame.otherWin = gmAbout
    1188                  gmAbout.Show(True)
    1189                  del gmAbout
    1190          #----------------------------------------------
    1191 -        def __on_about_database(self, evt):
    1192                  praxis = gmPraxis.gmCurrentPraxisBranch()
    1193                  msg = praxis.db_logon_banner
    1194
    1195                  login = gmPG2.get_default_login()
    1196
    1197                  auth = _(
    1198                          '\n\n'
    1199                          ' praxis:       %s\n'
    1200                          ' branch:       %s\n'
    1201                          ' workplace:    %s\n'
    1202                          ' account:      %s\n'
    1203                          ' database:     %s\n'
    1204                          ' server:       %s\n'
    1205                          ' PostgreSQL:   %s\n'
    1206                  ) % (
    1207                          praxis['praxis'],
    1208                          praxis['branch'],
    1209                          praxis.active_workplace,
    1210                          login.user,
    1211                          login.database,
    1212                          gmTools.coalesce(login.host, u'<localhost>'),
    1213                          gmPG2.postgresql_version_string
    1214                  )
    1215
    1216                  msg += auth
    1217
    1218                  gmGuiHelpers.gm_show_info(msg, _('About database and server'))
    1219          #----------------------------------------------
    1220 -        def __on_show_contributors(self, event):
    1221                  from Gnumed.wxpython import gmAbout
    1222                  contribs = gmAbout.cContributorsDlg (
    1223                          parent = self,
    1224                          id = -1,
    1225                          title = _('GNUmed contributors'),
    1226                          size = wx.Size(400,600),
    1227                          style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
    1228                  )
    1229                  contribs.ShowModal()
    1230                  del contribs
    1231                  del gmAbout
    1232          #----------------------------------------------
    1233          # GNUmed menu
    1234          #----------------------------------------------
    1235 -        def __on_exit_gnumed(self, event):
    1236                  """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
    1237                  _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
    1238                  self.Close(True)        # -> calls wx.EVT_CLOSE handler
    1239                  _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
    1240          #----------------------------------------------
    1241 -        def __on_check_for_updates(self, evt):
    1242                  gmCfgWidgets.check_for_updates()
    1243          #----------------------------------------------
    1244 -        def __on_announce_maintenance(self, evt):
    1245                  send = gmGuiHelpers.gm_show_question (
    1246                          _('This will send a notification about database downtime\n'
    1247                            'to all GNUmed clients connected to your database.\n'
    1248                            '\n'
    1249                            'Do you want to send the notification ?\n'
    1250                          ),
    1251                          _('Announcing database maintenance downtime')
    1252                  )
    1253                  if not send:
    1254                          return
    1255                  gmPG2.send_maintenance_notification()
    1256          #----------------------------------------------
    1257          #----------------------------------------------
    1258 -        def __on_list_configuration(self, evt):
    1259                  gmCfgWidgets.list_configuration(parent = self)
    1260          #----------------------------------------------
    1261          # submenu GNUmed / options / client
    1262          #----------------------------------------------
    1263 -        def __on_configure_export_chunk_size(self, evt):
    1264
    1265                  def is_valid(value):
    1266                          try:
    1267                                  i = int(value)
    1268                          except:
    1269                                  return False, value
    1270                          if i < 0:
    1271                                  return False, value
    1272                          if i > (1024 * 1024 * 1024 * 10):               # 10 GB
    1273                                  return False, value
    1274                          return True, i
    1275
    1276                  gmCfgWidgets.configure_string_option (
    1277                          message = _(
    1278                                  'Some network installations cannot cope with loading\n'
    1279                                  'documents of arbitrary size in one piece from the\n'
    1280                                  'database (mainly observed on older Windows versions)\n.'
    1281                                  '\n'
    1282                                  'Under such circumstances documents need to be retrieved\n'
    1283                                  'in chunks and reassembled on the client.\n'
    1284                                  '\n'
    1285                                  'Here you can set the size (in Bytes) above which\n'
    1286                                  'GNUmed will retrieve documents in chunks. Setting this\n'
    1287                                  'value to 0 will disable the chunking protocol.'
    1288                          ),
    1289                          option = 'horstspace.blob_export_chunk_size',
    1290                          bias = 'workplace',
    1291                          default_value = 1024 * 1024,
    1292                          validator = is_valid
    1293                  )
    1294          #----------------------------------------------
    1295          # submenu GNUmed / database
    1296          #----------------------------------------------
    1297 -        def __on_configure_db_lang(self, event):
    1298
    1299                  langs = gmPG2.get_translation_languages()
    1300
    1301                  for lang in [
    1302                          gmI18N.system_locale_level['language'],
    1303                          gmI18N.system_locale_level['country'],
    1304                          gmI18N.system_locale_level['full']
    1305                  ]:
    1306                          if lang not in langs:
    1307                                  langs.append(lang)
    1308
    1309                  selected_lang = gmPG2.get_current_user_language()
    1310                  try:
    1311                          selections = [langs.index(selected_lang)]
    1312                  except ValueError:
    1313                          selections = None
    1314
    1315                  language = gmListWidgets.get_choices_from_list (
    1316                          parent = self,
    1317                          msg = _(
    1318                                  'Please select your database language from the list below.\n'
    1319                                  '\n'
    1320                                  'Your current setting is [%s].\n'
    1321                                  '\n'
    1322                                  'This setting will not affect the language the user interface\n'
    1323                                  'is displayed in but rather that of the metadata returned\n'
    1324                                  'from the database such as encounter types, document types,\n'
    1325                                  'and EMR formatting.\n'
    1326                                  '\n'
    1327                                  'To switch back to the default English language unselect all\n'
    1328                                  'pre-selected languages from the list below.'
    1329                          ) % gmTools.coalesce(selected_lang, _('not configured')),
    1330                          caption = _('Configuring database language'),
    1331                          choices = langs,
    1332                          selections = selections,
    1333                          columns = [_('Language')],
    1334                          data = langs,
    1335                          single_selection = True,
    1336                          can_return_empty = True
    1337                  )
    1338
    1339                  if language is None:
    1340                          return
    1341
    1342                  if language == []:
    1343                          language = None
    1344
    1345                  try:
    1346                          _provider.get_staff().database_language = language
    1347                          return
    1348                  except ValueError:
    1349                          pass
    1350
    1351                  force_language = gmGuiHelpers.gm_show_question (
    1352                          _('The database currently holds no translations for\n'
    1353                            'language [%s]. However, you can add translations\n'
    1354                            'for things like document or encounter types yourself.\n'
    1355                            '\n'
    1356                            'Do you want to force the language setting to [%s] ?'
    1357                          ) % (language, language),
    1358                          _('Configuring database language')
    1359                  )
    1360                  if not force_language:
    1361                          return
    1362
    1363                  gmPG2.force_user_language(language = language)
    1364          #----------------------------------------------
    1365 -        def __on_configure_db_welcome(self, event):
    1366                  dlg = gmPraxisWidgets.cGreetingEditorDlg(self, -1)
    1367                  dlg.ShowModal()
    1368          #----------------------------------------------
    1369          # submenu GNUmed - config - external tools
    1370          #----------------------------------------------
    1371 -        def __on_configure_ooo_settle_time(self, event):
    1372
    1373                  def is_valid(value):
    1374                          try:
    1375                                  value = float(value)
    1376                                  return True, value
    1377                          except:
    1378                                  return False, value
    1379
    1380                  gmCfgWidgets.configure_string_option (
    1381                          message = _(
    1382                                  'When GNUmed cannot find an OpenOffice server it\n'
    1383                                  'will try to start one. OpenOffice, however, needs\n'
    1384                                  'some time to fully start up.\n'
    1385                                  '\n'
    1386                                  'Here you can set the time for GNUmed to wait for OOo.\n'
    1387                          ),
    1388                          option = 'external.ooo.startup_settle_time',
    1389                          bias = 'workplace',
    1390                          default_value = 2.0,
    1391                          validator = is_valid
    1392                  )
    1393          #----------------------------------------------
    1394 -        def __on_configure_drug_data_source(self, evt):
    1395                  gmMedicationWidgets.configure_drug_data_source(parent = self)
    1396          #----------------------------------------------
    1397 -        def __on_configure_adr_url(self, evt):
    1398
    1399                  # http://www.akdae.de/Arzneimittelsicherheit/UAW-Meldung/UAW-Meldung-online.html
    1400                  german_default = u'https://dcgma.org/uaw/meldung.php'
    1401
    1402                  def is_valid(value):
    1403                          value = value.strip()
    1404                          if value == u'':
    1405                                  return True, german_default
    1406                          try:
    1407                                  urllib2.urlopen(value)
    1408                                  return True, value
    1409                          except:
    1410                                  return True, value
    1411
    1412                  gmCfgWidgets.configure_string_option (
    1413                          message = _(
    1414                                  'GNUmed will use this URL to access a website which lets\n'
    1415                                  'you report an adverse drug reaction (ADR).\n'
    1416                                  '\n'
    1417                                  'If you leave this empty it will fall back\n'
    1418                                  'to an URL for reporting ADRs in Germany.'
    1419                          ),
    1420                          option = 'external.urls.report_ADR',
    1421                          bias = 'user',
    1422                          default_value = german_default,
    1423                          validator = is_valid
    1424                  )
    1425          #----------------------------------------------
    1426 -        def __on_configure_vaccine_adr_url(self, evt):
    1427
    1428                  german_default = u'http://www.pei.de/cln_042/SharedDocs/Downloads/fachkreise/uaw/meldeboegen/b-ifsg-meldebogen,templateId=raw,property=publicationFile.pdf/b-ifsg-meldebogen.pdf'
    1429
    1430                  def is_valid(value):
    1431                          value = value.strip()
    1432                          if value == u'':
    1433                                  return True, german_default
    1434                          try:
    1435                                  urllib2.urlopen(value)
    1436                                  return True, value
    1437                          except:
    1438                                  return True, value
    1439
    1440                  gmCfgWidgets.configure_string_option (
    1441                          message = _(
    1442                                  'GNUmed will use this URL to access a website which lets\n'
    1443                                  'you report an adverse vaccination reaction (vADR).\n'
    1444                                  '\n'
    1445                                  'If you set it to a specific address that URL must be\n'
    1446                                  'accessible now. If you leave it empty it will fall back\n'
    1447                                  'to the URL for reporting other adverse drug reactions.'
    1448                          ),
    1449                          option = 'external.urls.report_vaccine_ADR',
    1450                          bias = 'user',
    1451                          default_value = german_default,
    1452                          validator = is_valid
    1453                  )
    1454          #----------------------------------------------
    1455 -        def __on_configure_measurements_url(self, evt):
    1456
    1457                  german_default = u'http://www.laborlexikon.de',
    1458
    1459                  def is_valid(value):
    1460                          value = value.strip()
    1461                          if value == u'':
    1462                                  return True, german_default
    1463                          try:
    1464                                  urllib2.urlopen(value)
    1465                                  return True, value
    1466                          except:
    1467                                  return True, value
    1468
    1469                  gmCfgWidgets.configure_string_option (
    1470                          message = _(
    1471                                  'GNUmed will use this URL to access an encyclopedia of\n'
    1472                                  'measurement/lab methods from within the measurments grid.\n'
    1473                                  '\n'
    1474                                  'You can leave this empty but to set it to a specific\n'
    1475                                  'address the URL must be accessible now.'
    1476                          ),
    1477                          option = 'external.urls.measurements_encyclopedia',
    1478                          bias = 'user',
    1479                          default_value = german_default,
    1480                          validator = is_valid
    1481                  )
    1482          #----------------------------------------------
    1483 -        def __on_configure_vaccination_plans_url(self, evt):
    1484
    1485                  german_default = u'http://www.bundesaerztekammer.de/downloads/ImpfempfehlungenRKI2009.pdf'
    1486
    1487                  def is_valid(value):
    1488                          value = value.strip()
    1489                          if value == u'':
    1490                                  return True, german_default
    1491                          try:
    1492                                  urllib2.urlopen(value)
    1493                                  return True, value
    1494                          except:
    1495                                  return True, value
    1496
    1497                  gmCfgWidgets.configure_string_option (
    1498                          message = _(
    1499                                  'GNUmed will use this URL to access a page showing\n'
    1500                                  'vaccination schedules.\n'
    1501                                  '\n'
    1502                                  'You can leave this empty but to set it to a specific\n'
    1503                                  'address the URL must be accessible now.'
    1504                          ),
    1505                          option = 'external.urls.vaccination_plans',
    1506                          bias = 'user',
    1507                          default_value = german_default,
    1508                          validator = is_valid
    1509                  )
    1510          #----------------------------------------------
    1511 -        def __on_configure_acs_risk_calculator_cmd(self, event):
    1512
    1513                  def is_valid(value):
    1514                          found, binary = gmShellAPI.detect_external_binary(value)
    1515                          if not found:
    1516                                  gmDispatcher.send (
    1517                                          signal = 'statustext',
    1518                                          msg = _('The command [%s] is not found. This may or may not be a problem.') % value,
    1519                                          beep = True
    1520                                  )
    1521                                  return False, value
    1522                          return True, binary
    1523
    1524                  gmCfgWidgets.configure_string_option (
    1525                          message = _(
    1526                                  'Enter the shell command with which to start the\n'
    1527                                  'the ACS risk assessment calculator.\n'
    1528                                  '\n'
    1529                                  'GNUmed will try to verify the path which may,\n'
    1530                                  'however, fail if you are using an emulator such\n'
    1531                                  'as Wine. Nevertheless, starting the calculator\n'
    1532                                  'will work as long as the shell command is correct\n'
    1533                                  'despite the failing test.'
    1534                          ),
    1535                          option = 'external.tools.acs_risk_calculator_cmd',
    1536                          bias = 'user',
    1537                          validator = is_valid
    1538                  )
    1539          #----------------------------------------------
    1540 -        def __on_configure_visual_soap_cmd(self, event):
    1541                  gmNarrativeWidgets.configure_visual_progress_note_editor()
    1542          #----------------------------------------------
    1543 -        def __on_configure_freediams_cmd(self, event):
    1544
    1545                  def is_valid(value):
    1546                          found, binary = gmShellAPI.detect_external_binary(value)
    1547                          if not found:
    1548                                  gmDispatcher.send (
    1549                                          signal = 'statustext',
    1550                                          msg = _('The command [%s] is not found.') % value,
    1551                                          beep = True
    1552                                  )
    1553                                  return False, value
    1554                          return True, binary
    1555                  #------------------------------------------
    1556                  gmCfgWidgets.configure_string_option (
    1557                          message = _(
    1558                                  'Enter the shell command with which to start\n'
    1559                                  'the FreeDiams drug database frontend.\n'
    1560                                  '\n'
    1561                                  'GNUmed will try to verify that path.'
    1562                          ),
    1563                          option = 'external.tools.freediams_cmd',
    1564                          bias = 'workplace',
    1565                          default_value = None,
    1566                          validator = is_valid
    1567                  )
    1568          #----------------------------------------------
    1569 -        def __on_configure_ifap_cmd(self, event):
    1570
    1571                  def is_valid(value):
    1572                          found, binary = gmShellAPI.detect_external_binary(value)
    1573                          if not found:
    1574                                  gmDispatcher.send (
    1575                                          signal = 'statustext',
    1576                                          msg = _('The command [%s] is not found. This may or may not be a problem.') % value,
    1577                                          beep = True
    1578                                  )
    1579                                  return False, value
    1580                          return True, binary
    1581
    1582                  gmCfgWidgets.configure_string_option (
    1583                          message = _(
    1584                                  'Enter the shell command with which to start the\n'
    1585                                  'the IFAP drug database.\n'
    1586                                  '\n'
    1587                                  'GNUmed will try to verify the path which may,\n'
    1588                                  'however, fail if you are using an emulator such\n'
    1589                                  'as Wine. Nevertheless, starting IFAP will work\n'
    1590                                  'as long as the shell command is correct despite\n'
    1591                                  'the failing test.'
    1592                          ),
    1593                          option = 'external.ifap-win.shell_command',
    1594                          bias = 'workplace',
    1595                          default_value = 'C:\Ifapwin\WIAMDB.EXE',
    1596                          validator = is_valid
    1597                  )
    1598          #----------------------------------------------
    1599          # submenu GNUmed / config / ui
    1600          #----------------------------------------------
    1601 -        def __on_configure_startup_plugin(self, evt):
    1602
    1603                  dbcfg = gmCfg.cCfgSQL()
    1604                  # get list of possible plugins
    1605                  plugin_list = gmTools.coalesce(dbcfg.get2 (
    1606                          option = u'horstspace.notebook.plugin_load_order',
    1607                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1608                          bias = 'user'
    1609                  ), [])
    1610
    1611                  # get current setting
    1612                  initial_plugin = gmTools.coalesce(dbcfg.get2 (
    1613                          option = u'horstspace.plugin_to_raise_after_startup',
    1614                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1615                          bias = 'user'
    1616                  ), u'gmEMRBrowserPlugin')
    1617                  try:
    1618                          selections = [plugin_list.index(initial_plugin)]
    1619                  except ValueError:
    1620                          selections = None
    1621
    1622                  # now let user decide
    1623                  plugin = gmListWidgets.get_choices_from_list (
    1624                          parent = self,
    1625                          msg = _(
    1626                                  'Here you can choose which plugin you want\n'
    1627                                  'GNUmed to display after initial startup.\n'
    1628                                  '\n'
    1629                                  'Note that the plugin must not require any\n'
    1630                                  'patient to be activated.\n'
    1631                                  '\n'
    1632                                  'Select the desired plugin below:'
    1633                          ),
    1634                          caption = _('Configuration'),
    1635                          choices = plugin_list,
    1636                          selections = selections,
    1637                          columns = [_('GNUmed Plugin')],
    1638                          single_selection = True
    1639                  )
    1640
    1641                  if plugin is None:
    1642                          return
    1643
    1644                  dbcfg.set (
    1645                          option = u'horstspace.plugin_to_raise_after_startup',
    1646                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1647                          value = plugin
    1648                  )
    1649          #----------------------------------------------
    1650          # submenu GNUmed / config / ui / patient search
    1651          #----------------------------------------------
    1652 -        def __on_configure_quick_pat_search(self, evt):
    1653                  gmCfgWidgets.configure_boolean_option (
    1654                          parent = self,
    1655                          question = _(
    1656                                  'If there is only one external patient\n'
    1657                                  'source available do you want GNUmed\n'
    1658                                  'to immediately go ahead and search for\n'
    1659                                  'matching patient records ?\n\n'
    1660                                  'If not GNUmed will let you confirm the source.'
    1661                          ),
    1662                          option = 'patient_search.external_sources.immediately_search_if_single_source',
    1663                          button_tooltips = [
    1664                                  _('Yes, search for matches immediately.'),
    1665                                  _('No, let me confirm the external patient first.')
    1666                          ]
    1667                  )
    1668          #----------------------------------------------
    1669 -        def __on_cfg_default_region(self, evt):
    1670                  gmAddressWidgets.configure_default_region()
    1671          #----------------------------------------------
    1672 -        def __on_cfg_default_country(self, evt):
    1673                  gmAddressWidgets.configure_default_country()
    1674          #----------------------------------------------
    1675 -        def __on_configure_dob_reminder_proximity(self, evt):
    1676
    1677                  def is_valid(value):
    1678                          return gmPG2.is_pg_interval(candidate=value), value
    1679
    1680                  gmCfgWidgets.configure_string_option (
    1681                          message = _(
    1682                                  'When a patient is activated GNUmed checks the\n'
    1683                                  "proximity of the patient's birthday.\n"
    1684                                  '\n'
    1685                                  'If the birthday falls within the range of\n'
    1686                                  ' "today %s <the interval you set here>"\n'
    1687                                  'GNUmed will remind you of the recent or\n'
    1688                                  'imminent anniversary.'
    1689                          ) % u'\u2213',
    1690                          option = u'patient_search.dob_warn_interval',
    1691                          bias = 'user',
    1692                          default_value = '1 week',
    1693                          validator = is_valid
    1694                  )
    1695          #----------------------------------------------
    1696 -        def __on_allow_multiple_new_episodes(self, evt):
    1697
    1698                  gmCfgWidgets.configure_boolean_option (
    1699                          parent = self,
    1700                          question = _(
    1701                                  'When adding progress notes do you want to\n'
    1702                                  'allow opening several unassociated, new\n'
    1703                                  'episodes for a patient at once ?\n'
    1704                                  '\n'
    1705                                  'This can be particularly helpful when entering\n'
    1706                                  'progress notes on entirely new patients presenting\n'
    1707                                  'with a multitude of problems on their first visit.'
    1708                          ),
    1709                          option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
    1710                          button_tooltips = [
    1711                                  _('Yes, allow for multiple new episodes concurrently.'),
    1712                                  _('No, only allow editing one new episode at a time.')
    1713                          ]
    1714                  )
    1715          #----------------------------------------------
    1716 -        def __on_allow_auto_open_episodes(self, evt):
    1717
    1718                  gmCfgWidgets.configure_boolean_option (
    1719                          parent = self,
    1720                          question = _(
    1721                                  'When activating a patient, do you want GNUmed to\n'
    1722                                  'auto-open editors for all active problems that were\n'
    1723                                  'touched upon during the current and the most recent\n'
    1724                                  'encounter ?'
    1725                          ),
    1726                          option = u'horstspace.soap_editor.auto_open_latest_episodes',
    1727                          button_tooltips = [
    1728                                  _('Yes, auto-open editors for all problems of the most recent encounter.'),
    1729                                  _('No, only auto-open one editor for a new, unassociated problem.')
    1730                          ]
    1731                  )
    1732          #----------------------------------------------
    1733 -        def __on_configure_initial_pat_plugin(self, evt):
    1734
    1735                  dbcfg = gmCfg.cCfgSQL()
    1736                  # get list of possible plugins
    1737                  plugin_list = gmTools.coalesce(dbcfg.get2 (
    1738                          option = u'horstspace.notebook.plugin_load_order',
    1739                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1740                          bias = 'user'
    1741                  ), [])
    1742
    1743                  # get current setting
    1744                  initial_plugin = gmTools.coalesce(dbcfg.get2 (
    1745                          option = u'patient_search.plugin_to_raise_after_search',
    1746                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1747                          bias = 'user'
    1748                  ), u'gmPatientOverviewPlugin')
    1749                  try:
    1750                          selections = [plugin_list.index(initial_plugin)]
    1751                  except ValueError:
    1752                          selections = None
    1753
    1754                  # now let user decide
    1755                  plugin = gmListWidgets.get_choices_from_list (
    1756                          parent = self,
    1757                          msg = _(
    1758                                  'When a patient is activated GNUmed can\n'
    1759                                  'be told to switch to a specific plugin.\n'
    1760                                  '\n'
    1761                                  'Select the desired plugin below:'
    1762                          ),
    1763                          caption = _('Configuration'),
    1764                          choices = plugin_list,
    1765                          selections = selections,
    1766                          columns = [_('GNUmed Plugin')],
    1767                          single_selection = True
    1768                  )
    1769
    1770                  if plugin is None:
    1771                          return
    1772
    1773                  dbcfg.set (
    1774                          option = u'patient_search.plugin_to_raise_after_search',
    1775                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    1776                          value = plugin
    1777                  )
    1778          #----------------------------------------------
    1779          # submenu GNUmed / config / billing
    1780          #----------------------------------------------
    1781 -        def __on_cfg_invoice_template_no_vat(self, evt):
    1782                  gmBillingWidgets.configure_invoice_template(parent = self, with_vat = False)
    1783          #----------------------------------------------
    1784 -        def __on_cfg_invoice_template_with_vat(self, evt):
    1785                  gmBillingWidgets.configure_invoice_template(parent = self, with_vat = True)
    1786          #----------------------------------------------
    1787 -        def __on_configure_billing_catalogs_url(self, evt):
    1788                  german_default = u'http://www.e-bis.de/goae/defaultFrame.htm'
    1789
    1790                  def is_valid(value):
    1791                          value = value.strip()
    1792                          if value == u'':
    1793                                  return True, german_default
    1794                          try:
    1795                                  urllib2.urlopen(value)
    1796                                  return True, value
    1797                          except:
    1798                                  return True, value
    1799
    1800                  gmCfgWidgets.configure_string_option (
    1801                          message = _(
    1802                                  'GNUmed will use this URL to let you browse\n'
    1803                                  'billing catalogs (schedules of fees).\n'
    1804                                  '\n'
    1805                                  'You can leave this empty but to set it to a specific\n'
    1806                                  'address the URL must be accessible now.'
    1807                          ),
    1808                          option = 'external.urls.schedules_of_fees',
    1809                          bias = 'user',
    1810                          default_value = german_default,
    1811                          validator = is_valid
    1812                  )
    1813          #----------------------------------------------
    1814          # submenu GNUmed / config / encounter
    1815          #----------------------------------------------
    1816 -        def __on_cfg_medication_list_template(self, evt):
    1817                  gmMedicationWidgets.configure_medication_list_template(parent = self)
    1818          #----------------------------------------------
    1819 -        def __on_cfg_prescription_template(self, evt):
    1820                  gmMedicationWidgets.configure_prescription_template(parent = self)
    1821          #----------------------------------------------
    1822 -        def __on_cfg_prescription_mode(self, evt):
    1823                  gmCfgWidgets.configure_string_from_list_option (
    1824                          parent = self,
    1825                          message = _('Select the default prescription mode.\n'),
    1826                          option = 'horst_space.default_prescription_mode',
    1827                          bias = 'user',
    1828                          default_value = u'form',
    1829                          choices = [ _('Formular'), _('Datenbank') ],
    1830                          columns = [_('Prescription mode')],
    1831                          data = [ u'form', u'database' ]
    1832                  )
    1833          #----------------------------------------------
    1834 -        def __on_cfg_default_gnuplot_template(self, evt):
    1835                  gmMeasurementWidgets.configure_default_gnuplot_template(parent = self)
    1836          #----------------------------------------------
    1837 -        def __on_cfg_fallback_primary_provider(self, evt):
    1838                  gmPraxisWidgets.configure_fallback_primary_provider(parent = self)
    1839          #----------------------------------------------
    1840 -        def __on_cfg_enc_default_type(self, evt):
    1841                  enc_types = gmEMRStructItems.get_encounter_types()
    1842                  msg = _(
    1843                          'Select the default type for new encounters.\n'
    1844                          '\n'
    1845                          'Leaving this unset will make GNUmed apply the most commonly used type.\n'
    1846                  )
    1847                  gmCfgWidgets.configure_string_from_list_option (
    1848                          parent = self,
    1849                          message = msg,
    1850                          option = 'encounter.default_type',
    1851                          bias = 'user',
    1852  #                       default_value = u'in surgery',
    1853                          choices = [ e[0] for e in enc_types ],
    1854                          columns = [_('Encounter type')],
    1855                          data = [ e[1] for e in enc_types ]
    1856                  )
    1857          #----------------------------------------------
    1858 -        def __on_cfg_enc_pat_change(self, event):
    1859                  gmCfgWidgets.configure_boolean_option (
    1860                          parent = self,
    1861                          question = _(
    1862                                  'Do you want GNUmed to show the encounter\n'
    1863                                  'details editor when changing the active patient ?'
    1864                          ),
    1865                          option = 'encounter.show_editor_before_patient_change',
    1866                          button_tooltips = [
    1867                                  _('Yes, show the encounter editor if it seems appropriate.'),
    1868                                  _('No, never show the encounter editor even if it would seem useful.')
    1869                          ]
    1870                  )
    1871          #----------------------------------------------
    1872 -        def __on_cfg_enc_empty_ttl(self, evt):
    1873
    1874                  def is_valid(value):
    1875                          return gmPG2.is_pg_interval(candidate=value), value
    1876
    1877                  gmCfgWidgets.configure_string_option (
    1878                          message = _(
    1879                                  'When a patient is activated GNUmed checks the\n'
    1880                                  'chart for encounters lacking any entries.\n'
    1881                                  '\n'
    1882                                  'Any such encounters older than what you set\n'
    1883                                  'here will be removed from the medical record.\n'
    1884                                  '\n'
    1885                                  'To effectively disable removal of such encounters\n'
    1886                                  'set this option to an improbable value.\n'
    1887                          ),
    1888                          option = 'encounter.ttl_if_empty',
    1889                          bias = 'user',
    1890                          default_value = '1 week',
    1891                          validator = is_valid
    1892                  )
    1893          #----------------------------------------------
    1894 -        def __on_cfg_enc_min_ttl(self, evt):
    1895
    1896                  def is_valid(value):
    1897                          return gmPG2.is_pg_interval(candidate=value), value
    1898
    1899                  gmCfgWidgets.configure_string_option (
    1900                          message = _(
    1901                                  'When a patient is activated GNUmed checks the\n'
    1902                                  'age of the most recent encounter.\n'
    1903                                  '\n'
    1904                                  'If that encounter is younger than this age\n'
    1905                                  'the existing encounter will be continued.\n'
    1906                                  '\n'
    1907                                  '(If it is really old a new encounter is\n'
    1908                                  ' started, or else GNUmed will ask you.)\n'
    1909                          ),
    1910                          option = 'encounter.minimum_ttl',
    1911                          bias = 'user',
    1912                          default_value = '1 hour 30 minutes',
    1913                          validator = is_valid
    1914                  )
    1915          #----------------------------------------------
    1916 -        def __on_cfg_enc_max_ttl(self, evt):
    1917
    1918                  def is_valid(value):
    1919                          return gmPG2.is_pg_interval(candidate=value), value
    1920
    1921                  gmCfgWidgets.configure_string_option (
    1922                          message = _(
    1923                                  'When a patient is activated GNUmed checks the\n'
    1924                                  'age of the most recent encounter.\n'
    1925                                  '\n'
    1926                                  'If that encounter is older than this age\n'
    1927                                  'GNUmed will always start a new encounter.\n'
    1928                                  '\n'
    1929                                  '(If it is very recent the existing encounter\n'
    1930                                  ' is continued, or else GNUmed will ask you.)\n'
    1931                          ),
    1932                          option = 'encounter.maximum_ttl',
    1933                          bias = 'user',
    1934                          default_value = '6 hours',
    1935                          validator = is_valid
    1936                  )
    1937          #----------------------------------------------
    1938 -        def __on_cfg_epi_ttl(self, evt):
    1939
    1940                  def is_valid(value):
    1941                          try:
    1942                                  value = int(value)
    1943                          except:
    1944                                  return False, value
    1945                          return gmPG2.is_pg_interval(candidate=value), value
    1946
    1947                  gmCfgWidgets.configure_string_option (
    1948                          message = _(
    1949                                  'At any time there can only be one open (ongoing)\n'
    1950                                  'episode for each health issue.\n'
    1951                                  '\n'
    1952                                  'When you try to open (add data to) an episode on a health\n'
    1953                                  'issue GNUmed will check for an existing open episode on\n'
    1954                                  'that issue. If there is any it will check the age of that\n'
    1955                                  'episode. The episode is closed if it has been dormant (no\n'
    1956                                  'data added, that is) for the period of time (in days) you\n'
    1957                                  'set here.\n'
    1958                                  '\n'
    1959                                  "If the existing episode hasn't been dormant long enough\n"
    1960                                  'GNUmed will consult you what to do.\n'
    1961                                  '\n'
    1962                                  'Enter maximum episode dormancy in DAYS:'
    1963                          ),
    1964                          option = 'episode.ttl',
    1965                          bias = 'user',
    1966                          default_value = 60,
    1967                          validator = is_valid
    1968                  )
    1969          #----------------------------------------------
    1970 -        def __on_configure_user_email(self, evt):
    1971                  email = gmPraxis.gmCurrentPraxisBranch().user_email
    1972
    1973                  dlg = wx.TextEntryDialog (
    1974                          parent = self,
    1975                          message = _(
    1976                                  'If you want the GNUmed developers to be able to\n'
    1977                                  'contact you directly - rather than via the public\n'
    1978                                  'mailing list only - you can enter your preferred\n'
    1979                                  'email address here.\n'
    1980                                  '\n'
    1981                                  'This address will then be included with bug reports\n'
    1982                                  'or contributions to the GNUmed community you may\n'
    1983                                  'choose to send from within the GNUmed client.\n'
    1984                                  '\n'
    1985                                  'Leave this blank if you wish to stay anonymous.\n'
    1986                          ),
    1987                          caption = _('Please enter your email address.'),
    1988                          defaultValue = gmTools.coalesce(email, u''),
    1989                          style = wx.OK | wx.CANCEL | wx.CENTRE
    1990                  )
    1991                  decision = dlg.ShowModal()
    1992                  if decision == wx.ID_CANCEL:
    1993                          dlg.Destroy()
    1994                          return
    1995
    1996                  email = dlg.GetValue().strip()
    1997                  gmPraxis.gmCurrentPraxisBranch().user_email = email
    1998                  gmExceptionHandlingWidgets.set_sender_email(email)
    1999                  dlg.Destroy()
    2000          #----------------------------------------------
    2001 -        def __on_configure_update_check(self, evt):
    2002                  gmCfgWidgets.configure_boolean_option (
    2003                          question = _(
    2004                                  'Do you want GNUmed to check for updates at startup ?\n'
    2005                                  '\n'
    2006                                  'You will still need your system administrator to\n'
    2007                                  'actually install any updates for you.\n'
    2008                          ),
    2009                          option = u'horstspace.update.autocheck_at_startup',
    2010                          button_tooltips = [
    2011                                  _('Yes, check for updates at startup.'),
    2012                                  _('No, do not check for updates at startup.')
    2013                          ]
    2014                  )
    2015          #----------------------------------------------
    2016 -        def __on_configure_update_check_scope(self, evt):
    2017                  gmCfgWidgets.configure_boolean_option (
    2018                          question = _(
    2019                                  'When checking for updates do you want GNUmed to\n'
    2020                                  'look for bug fix updates only or do you want to\n'
    2021                                  'know about features updates, too ?\n'
    2022                                  '\n'
    2023                                  'Minor updates (x.y.z.a -> x.y.z.b) contain bug fixes\n'
    2024                                  'only. They can usually be installed without much\n'
    2025                                  'preparation. They never require a database upgrade.\n'
    2026                                  '\n'
    2027                                  'Major updates (x.y.a -> x..y.b or y.a -> x.b) come\n'
    2028                                  'with new features. They need more preparation and\n'
    2029                                  'often require a database upgrade.\n'
    2030                                  '\n'
    2031                                  'You will still need your system administrator to\n'
    2032                                  'actually install any updates for you.\n'
    2033                          ),
    2034                          option = u'horstspace.update.consider_latest_branch',
    2035                          button_tooltips = [
    2036                                  _('Yes, check for feature updates, too.'),
    2037                                  _('No, check for bug-fix updates only.')
    2038                          ]
    2039                  )
    2040          #----------------------------------------------
    2041 -        def __on_configure_update_url(self, evt):
    2042
    2043                  import urllib2 as url
    2044
    2045                  def is_valid(value):
    2046                          try:
    2047                                  url.urlopen(value)
    2048                          except:
    2049                                  return False, value
    2050
    2051                          return True, value
    2052
    2053                  gmCfgWidgets.configure_string_option (
    2054                          message = _(
    2055                                  'GNUmed can check for new releases being available. To do\n'
    2056                                  'so it needs to load version information from an URL.\n'
    2057                                  '\n'
    2058                                  'The default URL is:\n'
    2059                                  '\n'
    2060                                  ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
    2061                                  '\n'
    2062                                  'but you can configure any other URL locally. Note\n'
    2063                                  'that you must enter the location as a valid URL.\n'
    2064                                  'Depending on the URL the client will need online\n'
    2065                                  'access when checking for updates.'
    2066                          ),
    2067                          option = u'horstspace.update.url',
    2068                          bias = u'workplace',
    2069                          default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
    2070                          validator = is_valid
    2071                  )
    2072          #----------------------------------------------
    2073 -        def __on_configure_partless_docs(self, evt):
    2074                  gmCfgWidgets.configure_boolean_option (
    2075                          question = _(
    2076                                  'Do you want to allow saving of new documents without\n'
    2077                                  'any parts or do you want GNUmed to enforce that they\n'
    2078                                  'contain at least one part before they can be saved ?\n'
    2079                                  '\n'
    2080                                  'Part-less documents can be useful if you want to build\n'
    2081                                  'up an index of, say, archived documents but do not\n'
    2082                                  'want to scan in all the pages contained therein.'
    2083                          ),
    2084                          option = u'horstspace.scan_index.allow_partless_documents',
    2085                          button_tooltips = [
    2086                                  _('Yes, allow saving documents without any parts.'),
    2087                                  _('No, require documents to have at least one part.')
    2088                          ]
    2089                  )
    2090          #----------------------------------------------
    2091 -        def __on_configure_doc_uuid_dialog(self, evt):
    2092                  gmCfgWidgets.configure_boolean_option (
    2093                          question = _(
    2094                                  'After importing a new document do you\n'
    2095                                  'want GNUmed to display the unique ID\n'
    2096                                  'it auto-generated for that document ?\n'
    2097                                  '\n'
    2098                                  'This can be useful if you want to label the\n'
    2099                                  'originals with that ID for later identification.'
    2100                          ),
    2101                          option = u'horstspace.scan_index.show_doc_id',
    2102                          button_tooltips = [
    2103                                  _('Yes, display the ID generated for the new document after importing.'),
    2104                                  _('No, do not display the ID generated for the new document after importing.')
    2105                          ]
    2106                  )
    2107          #----------------------------------------------
    2108 -        def __on_configure_generate_doc_uuid(self, evt):
    2109                  gmCfgWidgets.configure_boolean_option (
    2110                          question = _(
    2111                                  'After importing a new document do you\n'
    2112                                  'want GNUmed to generate a unique ID\n'
    2113                                  '(UUID) for that document ?\n'
    2114                                  '\n'
    2115                                  'This can be useful if you want to label the\n'
    2116                                  'originals with that ID for later identification.'
    2117                          ),
    2118                          option = u'horstspace.scan_index.generate_doc_uuid',
    2119                          button_tooltips = [
    2120                                  _('Yes, generate a UUID for the new document after importing.'),
    2121                                  _('No, do not generate a UUID for the new document after importing.')
    2122                          ]
    2123                  )
    2124          #----------------------------------------------
    2125 -        def __on_configure_doc_review_dialog(self, evt):
    2126
    2127                  def is_valid(value):
    2128                          try:
    2129                                  value = int(value)
    2130                          except:
    2131                                  return False, value
    2132                          if value not in [0, 1, 2, 3, 4]:
    2133                                  return False, value
    2134                          return True, value
    2135
    2136                  gmCfgWidgets.configure_string_option (
    2137                          message = _(
    2138                                  'GNUmed can show the document review dialog after\n'
    2139                                  'calling the appropriate viewer for that document.\n'
    2140                                  '\n'
    2141                                  'Select the conditions under which you want\n'
    2142                                  'GNUmed to do so:\n'
    2143                                  '\n'
    2144                                  ' 0: never display the review dialog\n'
    2145                                  ' 1: always display the dialog\n'
    2146                                  ' 2: only if there is no previous review by me\n'
    2147                                  ' 3: only if there is no previous review at all\n'
    2148                                  ' 4: only if there is no review by the responsible reviewer\n'
    2149                                  '\n'
    2150                                  'Note that if a viewer is configured to not block\n'
    2151                                  'GNUmed during document display the review dialog\n'
    2152                                  'will actually appear in parallel to the viewer.'
    2153                          ),
    2154                          option = u'horstspace.document_viewer.review_after_display',
    2155                          bias = u'user',
    2156                          default_value = 3,
    2157                          validator = is_valid
    2158                  )
    2159          #----------------------------------------------
    2160 -        def __on_manage_master_data(self, evt):
    2161
    2162                  # this is how it is sorted
    2163                  master_data_lists = [
    2164                          'adr',
    2165                          'billables',
    2166                          'drugs',
    2167                          'hints',
    2168                          'codes',
    2169                          'communication_channel_types',
    2170                          'substances_in_brands',
    2171                          'substances',
    2172                          'labs',
    2173                          'form_templates',
    2174                          'doc_types',
    2175                          'enc_types',
    2176                          'text_expansions',
    2177                          'meta_test_types',
    2178                          'orgs',
    2179                          'patient_tags',
    2180                          'provinces',
    2181                          'db_translations',
    2182                          'ref_data_sources',
    2183                          'test_types',
    2184                          'test_panels',
    2185                          'vacc_indications',
    2186                          'vaccines',
    2187                          'workplaces'
    2188                  ]
    2189
    2190                  master_data_list_names = {
    2191                          'adr': _('Addresses (likely slow)'),
    2192                          'drugs': _('Branded drugs (as marketed)'),
    2193                          'hints': _('Clinical hints'),
    2194                          'codes': _('Codes and their respective terms'),
    2195                          'communication_channel_types': _('Communication channel types'),
    2196                          'substances_in_brands': _('Components of branded drugs (substances in brands)'),
    2197                          'labs': _('Diagnostic organizations (path labs, ...)'),
    2198                          'form_templates': _('Document templates (forms, letters, plots, ...)'),
    2199                          'doc_types': _('Document types'),
    2200                          'enc_types': _('Encounter types'),
    2201                          'text_expansions': _('Keyword based text expansion macros'),
    2202                          'meta_test_types': _('Meta test/measurement types'),
    2203                          'orgs': _('Organizations with their units, addresses, and comm channels'),
    2204                          'patient_tags': _('Patient tags'),
    2205                          'provinces': _('Provinces (counties, territories, states, regions, ...)'),
    2206                          'db_translations': _('String translations in the database'),
    2207                          'test_types': _('Test/measurement types'),
    2208                          'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
    2209                          'vaccines': _('Vaccines'),
    2210                          'workplaces': _('Workplace profiles (which plugins to load)'),
    2211                          'substances': _('Consumable substances'),
    2212                          'billables': _('Billable items'),
    2213                          'ref_data_sources': _('Reference data sources'),
    2214                          'test_panels': _('Test/measurement panels/profiles')
    2215                  }
    2216
    2217                  map_list2handler = {
    2218                          'form_templates': gmFormWidgets.manage_form_templates,
    2219                          'doc_types': gmDocumentWidgets.manage_document_types,
    2220                          'text_expansions': gmKeywordExpansionWidgets.configure_keyword_text_expansion,
    2221                          'db_translations': gmI18nWidgets.manage_translations,
    2222                          'codes': gmCodingWidgets.browse_coded_terms,
    2223                          'enc_types': gmEMRStructWidgets.manage_encounter_types,
    2224                          'provinces': gmAddressWidgets.manage_provinces,
    2225                          'workplaces': gmPraxisWidgets.configure_workplace_plugins,
    2226                          'drugs': gmMedicationWidgets.manage_branded_drugs,
    2227                          'substances_in_brands': gmMedicationWidgets.manage_drug_components,
    2228                          'labs': gmMeasurementWidgets.manage_measurement_orgs,
    2229                          'test_types': gmMeasurementWidgets.manage_measurement_types,
    2230                          'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
    2231                          'vaccines': gmVaccWidgets.manage_vaccines,
    2232                          'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
    2233                          'orgs': gmOrganizationWidgets.manage_orgs,
    2234                          'adr': gmAddressWidgets.manage_addresses,
    2235                          'substances': gmMedicationWidgets.manage_consumable_substances,
    2236                          'patient_tags': gmDemographicsWidgets.manage_tag_images,
    2237                          'communication_channel_types': gmContactWidgets.manage_comm_channel_types,
    2238                          'billables': gmBillingWidgets.manage_billables,
    2239                          'ref_data_sources': gmCodingWidgets.browse_data_sources,
    2240                          'hints': gmProviderInboxWidgets.browse_dynamic_hints,
    2241                          'test_panels': gmMeasurementWidgets.manage_test_panels
    2242                  }
    2243
    2244                  #---------------------------------
    2245                  def edit(item):
    2246                          try: map_list2handler[item](parent = self)
    2247                          except KeyError: pass
    2248                          return False
    2249                  #---------------------------------
    2250
    2251                  gmListWidgets.get_choices_from_list (
    2252                          parent = self,
    2253                          caption = _('Master data management'),
    2254                          choices = [ master_data_list_names[lst] for lst in master_data_lists],
    2255                          data = master_data_lists,
    2256                          columns = [_('Select the list you want to manage:')],
    2257                          edit_callback = edit,
    2258                          single_selection = True,
    2259                          ignore_OK_button = True
    2260                  )
    2261          #----------------------------------------------
    2262 -        def __on_manage_praxis(self, evt):
    2263                  gmPraxisWidgets.manage_praxis_branches(parent = self)
    2264          #----------------------------------------------
    2265 -        def __on_dicom_viewer(self, evt):
    2266
    2267                  found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
    2268                  if found:
    2269                          gmShellAPI.run_command_in_shell(cmd, blocking=False)
    2270                          return
    2271
    2272                  if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
    2273                          gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking = False)
    2274                          return
    2275
    2276                  for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
    2277                          found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
    2278                          if found:
    2279                                  gmShellAPI.run_command_in_shell(cmd, blocking = False)
    2280                                  return
    2281
    2282                  gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
    2283          #----------------------------------------------
    2284 -        def __on_arriba(self, evt):
    2285
    2286                  curr_pat = gmPerson.gmCurrentPatient()
    2287
    2288                  arriba = gmArriba.cArriba()
    2289                  pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
    2290                  if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
    2291                          return
    2292
    2293                  # FIXME: try to find patient
    2294                  if curr_pat is None:
    2295                          return
    2296
    2297                  if arriba.pdf_result is None:
    2298                          return
    2299
    2300                  doc = gmDocumentWidgets.save_file_as_new_document (
    2301                          parent = self,
    2302                          filename = arriba.pdf_result,
    2303                          document_type = _('risk assessment')
    2304                  )
    2305
    2306                  try: os.remove(arriba.pdf_result)
    2307                  except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
    2308
    2309                  if doc is None:
    2310                          return
    2311
    2312                  doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
    2313                  doc.save()
    2314
    2315                  try:
    2316                          open(arriba.xml_result).close()
    2317                          part = doc.add_part(file = arriba.xml_result)
    2318                  except StandardError:
    2319                          _log.exception('error accessing [%s]', arriba.xml_result)
    2320                          gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
    2321
    2322                  if part is None:
    2323                          return
    2324
    2325                  part['obj_comment'] = u'XML-Daten'
    2326                  part['filename'] = u'arriba-result.xml'
    2327                  part.save()
    2328          #----------------------------------------------
    2329 -        def __on_acs_risk_assessment(self, evt):
    2330
    2331                  dbcfg = gmCfg.cCfgSQL()
    2332                  cmd = dbcfg.get2 (
    2333                          option = u'external.tools.acs_risk_calculator_cmd',
    2334                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2335                          bias = 'user'
    2336                  )
    2337
    2338                  if cmd is None:
    2339                          gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
    2340                          return
    2341
    2342                  cwd = os.path.expanduser(os.path.join('~', '.gnumed'))
    2343                  try:
    2344                          subprocess.check_call (
    2345                                  args = (cmd,),
    2346                                  close_fds = True,
    2347                                  cwd = cwd
    2348                          )
    2349                  except (OSError, ValueError, subprocess.CalledProcessError):
    2350                          _log.exception('there was a problem executing [%s]', cmd)
    2351                          gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
    2352                          return
    2353
    2354                  pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
    2355                  for pdf in pdfs:
    2356                          try:
    2357                                  open(pdf).close()
    2358                          except:
    2359                                  _log.exception('error accessing [%s]', pdf)
    2360                                  gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
    2361                                  continue
    2362
    2363                          doc = gmDocumentWidgets.save_file_as_new_document (
    2364                                  parent = self,
    2365                                  filename = pdf,
    2366                                  document_type = u'risk assessment'
    2367                          )
    2368
    2369                          try:
    2370                                  os.remove(pdf)
    2371                          except StandardError:
    2372                                  _log.exception('cannot remove [%s]', pdf)
    2373
    2374                          if doc is None:
    2375                                  continue
    2376                          doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
    2377                          doc.save()
    2378
    2379                  return
    2380          #----------------------------------------------
    2381 -        def __on_excelleris(self, evt):
    2382                  gmMeasurementWidgets.import_Excelleris_HL7(parent = self)
    2383          #----------------------------------------------
    2384 -        def __on_hl7(self, evt):
    2385                  gmMeasurementWidgets.import_HL7(parent = self)
    2386          #----------------------------------------------
    2387 -        def __on_incoming(self, evt):
    2388                  gmMeasurementWidgets.browse_incoming_unmatched(parent = self)
    2389          #----------------------------------------------
    2390 -        def __on_snellen(self, evt):
    2391                  dlg = gmSnellen.cSnellenCfgDlg()
    2392                  if dlg.ShowModal() != wx.ID_OK:
    2393                          return
    2394
    2395                  frame = gmSnellen.cSnellenChart (
    2396                          width = dlg.vals[0],
    2397                          height = dlg.vals[1],
    2398                          alpha = dlg.vals[2],
    2399                          mirr = dlg.vals[3],
    2400                          parent = None
    2401                  )
    2402                  frame.CentreOnScreen(wx.BOTH)
    2403  #               self.SetTopWindow(frame)
    2404  #               frame.Destroy = frame.DestroyWhenApp
    2405                  frame.Show(True)
    2406          #----------------------------------------------
    2407          #----------------------------------------------
    2408 -        def __on_medical_links(self, evt):
    2409                  gmNetworkTools.open_url_in_browser(url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'])
    2410          #----------------------------------------------
    2411 -        def __on_jump_to_drug_db(self, evt):
    2412                  gmMedicationWidgets.jump_to_drug_database()
    2413          #----------------------------------------------
    2414 -        def __on_kompendium_ch(self, evt):
    2415                  gmNetworkTools.open_url_in_browser(url = u'http://www.kompendium.ch')
    2416          #----------------------------------------------
    2417          # Office
    2418          #----------------------------------------------
    2419 -        def __on_display_audit_trail(self, evt):
    2420                  gmAccessPermissionWidgets.show_audit_trail(parent = self)
    2421                  evt.Skip()
    2422          #----------------------------------------------
    2423 -        def __on_show_all_bills(self, evt):
    2424                  gmBillingWidgets.manage_bills(parent = self)
    2425          #----------------------------------------------
    2426          # Help / Debugging
    2427          #----------------------------------------------
    2428 -        def __on_save_screenshot(self, evt):
    2429                  wx.CallAfter(self.__save_screenshot)
    2430                  evt.Skip()
    2431          #----------------------------------------------
    2432 -        def __save_screenshot(self):
    2433
    2434                  time.sleep(0.5)
    2435
    2436                  rect = self.GetRect()
    2437
    2438                  # adjust for window decoration on Linux
    2439                  if sys.platform == 'linux2':
    2440                          client_x, client_y = self.ClientToScreen((0, 0))
    2441                          border_width = client_x - rect.x
    2442                          title_bar_height = client_y - rect.y
    2443                          # If the window has a menu bar, remove it from the title bar height.
    2444                          if self.GetMenuBar():
    2445                                  title_bar_height /= 2
    2446                          rect.width += (border_width * 2)
    2447                          rect.height += title_bar_height + border_width
    2448
    2449                  wdc = wx.ScreenDC()
    2450                  mdc = wx.MemoryDC()
    2451                  img = wx.EmptyBitmap(rect.width, rect.height)
    2452                  mdc.SelectObject(img)
    2453                  mdc.Blit (                                              # copy ...
    2454                          0, 0,                                           # ... to here in the target ...
    2455                          rect.width, rect.height,        # ... that much from ...
    2456                          wdc,                                            # ... the source ...
    2457                          rect.x, rect.y                          # ... starting here
    2458                  )
    2459
    2460                  # FIXME: improve filename with patient/workplace/provider, allow user to select/change
    2461                  fname = os.path.expanduser(os.path.join('~', 'gnumed', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
    2462                  img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
    2463                  gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
    2464          #----------------------------------------------
    2465 -        def __on_test_exception(self, evt):
    2466                  #import nonexistant_module
    2467                  raise ValueError('raised ValueError to test exception handling')
    2468          #----------------------------------------------
    2469 -        def __on_test_access_violation(self, evt):
    2470                  raise gmExceptions.AccessDenied (
    2471                          _('[-9999]: <access violation test error>'),
    2472                          source = u'GNUmed code',
    2473                          code = -9999,
    2474                          details = _('This is a deliberate AcessDenied exception thrown to test the handling of access violations by means of a decorator.')
    2475                  )
    2476          #----------------------------------------------
    2477          @gmAccessPermissionWidgets.verify_minimum_required_role('admin', activity = _('testing access check for non-existant <admin> role'))
    2478 -        def __on_test_access_checking(self, evt):
    2479                  raise gmExceptions.AccessDenied (
    2480                          _('[-9999]: <access violation test error>'),
    2481                          source = u'GNUmed code',
    2482                          code = -9999,
    2483                          details = _('This is a deliberate AcessDenied exception. You should not see this message because the role is checked in a decorator.')
    2484                  )
    2485          #----------------------------------------------
    2486 -        def __on_invoke_inspector(self, evt):
    2487                  import wx.lib.inspection
    2488                  wx.lib.inspection.InspectionTool().Show()
    2489          #----------------------------------------------
    2490 -        def __on_display_bugtracker(self, evt):
    2491                  gmNetworkTools.open_url_in_browser(url = 'https://bugs.launchpad.net/gnumed/')
    2492          #----------------------------------------------
    2493 -        def __on_display_wiki(self, evt):
    2494                  gmNetworkTools.open_url_in_browser(url = 'http://wiki.gnumed.de')
    2495          #----------------------------------------------
    2496 -        def __on_display_user_manual_online(self, evt):
    2497                  gmNetworkTools.open_url_in_browser(url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual')
    2498          #----------------------------------------------
    2501          #----------------------------------------------
    2502 -        def __on_pgadmin3(self, evt):
    2503                  found, cmd = gmShellAPI.detect_external_binary(binary = 'pgadmin3')
    2504                  if found:
    2505                          gmShellAPI.run_command_in_shell(cmd, blocking = False)
    2506                          return
    2507                  gmDispatcher.send(signal = 'statustext', msg = _('pgAdmin III not found.'), beep = True)
    2508          #----------------------------------------------
    2509 -        def __on_reload_hook_script(self, evt):
    2510                  if not gmHooks.import_hook_module(reimport = True):
    2511                          gmDispatcher.send(signal = 'statustext', msg = _('Error reloading hook script.'))
    2512          #----------------------------------------------
    2513 -        def __on_unblock_cursor(self, evt):
    2514                  wx.EndBusyCursor()
    2515          #----------------------------------------------
    2516 -        def __on_clear_status_line(self, evt):
    2517                  gmDispatcher.send(signal = 'statustext', msg = u'')
    2518          #----------------------------------------------
    2519 -        def __on_toggle_patient_lock(self, evt):
    2520                  curr_pat = gmPerson.gmCurrentPatient()
    2521                  if curr_pat.locked:
    2522                          curr_pat.force_unlock()
    2523                  else:
    2524                          curr_pat.locked = True
    2525          #----------------------------------------------
    2526 -        def __on_show_log_file(self, evt):
    2527                  from Gnumed.pycommon import gmMimeLib
    2528                  gmLog2.flush()
    2529                  gmMimeLib.call_viewer_on_file(gmLog2._logfile_name, block = False)
    2530          #----------------------------------------------
    2531 -        def __on_backup_log_file(self, evt):
    2532                  name = os.path.basename(gmLog2._logfile_name)
    2533                  name, ext = os.path.splitext(name)
    2534                  new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
    2535                  new_path = os.path.expanduser(os.path.join('~', 'gnumed'))
    2536
    2537                  dlg = wx.FileDialog (
    2538                          parent = self,
    2539                          message = _("Save current log as..."),
    2540                          defaultDir = new_path,
    2541                          defaultFile = new_name,
    2542                          wildcard = "%s (*.log)|*.log" % _("log files"),
    2543                          style = wx.SAVE
    2544                  )
    2545                  choice = dlg.ShowModal()
    2546                  new_name = dlg.GetPath()
    2547                  dlg.Destroy()
    2548                  if choice != wx.ID_OK:
    2549                          return True
    2550
    2551                  _log.warning('syncing log file for backup to [%s]', new_name)
    2552                  gmLog2.flush()
    2553                  shutil.copy2(gmLog2._logfile_name, new_name)
    2554                  gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
    2555          #----------------------------------------------
    2556 -        def __on_email_log_file(self, evt):
    2557                  gmExceptionHandlingWidgets.mail_log(parent = self)
    2558          #----------------------------------------------
    2559          # GNUmed /
    2560          #----------------------------------------------
    2561 -        def OnClose(self, event):
    2562                  """This is the wx.EVT_CLOSE handler.
    2563
    2564                  - framework still functional
    2565                  """
    2566                  _log.debug('gmTopLevelFrame.OnClose() start')
    2567                  self._clean_exit()
    2568                  self.Destroy()
    2569                  _log.debug('gmTopLevelFrame.OnClose() end')
    2570                  return True
    2571          #----------------------------------------------
    2572 -        def OnExportEMR(self, event):
    2573                  """
    2574                  Export selected patient EMR to a file
    2575                  """
    2576                  gmEMRBrowser.export_emr_to_ascii(parent=self)
    2577          #----------------------------------------------
    2578 -        def __dermtool (self, event):
    2579                  import Gnumed.wxpython.gmDermTool as DT
    2580                  frame = DT.DermToolDialog(None, -1)
    2581                  frame.Show(True)
    2582          #----------------------------------------------
    2583 -        def __on_start_new_encounter(self, evt):
    2584                  pat = gmPerson.gmCurrentPatient()
    2585                  if not pat.connected:
    2586                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot start new encounter. No active patient.'))
    2587                          return False
    2588                  emr = pat.get_emr()
    2589                  gmEMRStructWidgets.start_new_encounter(emr = emr)
    2590          #----------------------------------------------
    2591 -        def __on_list_encounters(self, evt):
    2592                  pat = gmPerson.gmCurrentPatient()
    2593                  if not pat.connected:
    2594                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
    2595                          return False
    2596                  gmEMRStructWidgets.select_encounters()
    2597          #----------------------------------------------
    2598 -        def __on_add_health_issue(self, event):
    2599                  pat = gmPerson.gmCurrentPatient()
    2600                  if not pat.connected:
    2601                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot add health issue. No active patient.'))
    2602                          return False
    2603                  gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
    2604          #----------------------------------------------
    2605 -        def __on_add_episode(self, event):
    2606                  pat = gmPerson.gmCurrentPatient()
    2607                  if not pat.connected:
    2608                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot add episode. No active patient.'))
    2609                          return False
    2610                  gmEMRStructWidgets.edit_episode(parent = self, episode = None)
    2611          #----------------------------------------------
    2612 -        def __on_add_medication(self, evt):
    2613                  pat = gmPerson.gmCurrentPatient()
    2614                  if not pat.connected:
    2615                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot add medication. No active patient.'))
    2616                          return False
    2617
    2618                  gmMedicationWidgets.edit_intake_of_substance(parent = self, substance = None)
    2619
    2620                  evt.Skip()
    2621          #----------------------------------------------
    2622 -        def __on_manage_allergies(self, evt):
    2623                  pat = gmPerson.gmCurrentPatient()
    2624                  if not pat.connected:
    2625                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot add allergy. No active patient.'))
    2626                          return False
    2627                  dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1)
    2628                  dlg.ShowModal()
    2629          #----------------------------------------------
    2630 -        def __on_manage_performed_procedures(self, evt):
    2631                  pat = gmPerson.gmCurrentPatient()
    2632                  if not pat.connected:
    2633                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage performed procedures. No active patient.'))
    2634                          return False
    2635                  gmEMRStructWidgets.manage_performed_procedures(parent = self)
    2636                  evt.Skip()
    2637          #----------------------------------------------
    2638 -        def __on_manage_hospital_stays(self, evt):
    2639                  pat = gmPerson.gmCurrentPatient()
    2640                  if not pat.connected:
    2641                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage hospitalizations. No active patient.'))
    2642                          return False
    2643                  gmEMRStructWidgets.manage_hospital_stays(parent = self)
    2644                  evt.Skip()
    2645          #----------------------------------------------
    2646 -        def __on_edit_occupation(self, evt):
    2647                  pat = gmPerson.gmCurrentPatient()
    2648                  if not pat.connected:
    2649                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit occupation. No active patient.'))
    2650                          return False
    2651                  gmDemographicsWidgets.edit_occupation()
    2652                  evt.Skip()
    2653          #----------------------------------------------
    2654          @gmAccessPermissionWidgets.verify_minimum_required_role('doctor', activity = _('manage vaccinations'))
    2655 -        def __on_add_vaccination(self, evt):
    2656                  pat = gmPerson.gmCurrentPatient()
    2657                  if not pat.connected:
    2658                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot add vaccinations. No active patient.'))
    2659                          return False
    2660
    2661                  gmVaccWidgets.manage_vaccinations(parent = self)
    2662                  evt.Skip()
    2663          #----------------------------------------------
    2664 -        def __on_manage_fhx(self, evt):
    2665                  pat = gmPerson.gmCurrentPatient()
    2666                  if not pat.connected:
    2667                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage family history. No active patient.'))
    2668                          return False
    2669
    2670                  gmFamilyHistoryWidgets.manage_family_history(parent = self)
    2671                  evt.Skip()
    2672          #----------------------------------------------
    2673 -        def __on_add_measurement(self, evt):
    2674                  pat = gmPerson.gmCurrentPatient()
    2675                  if not pat.connected:
    2676                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot add measurement. No active patient.'))
    2677                          return False
    2678                  gmMeasurementWidgets.edit_measurement(parent = self, measurement = None)
    2679                  evt.Skip()
    2680          #----------------------------------------------
    2681 -        def __on_manage_measurements(self, evt):
    2682                  pat = gmPerson.gmCurrentPatient()
    2683                  if not pat.connected:
    2684                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage measurements. No active patient.'))
    2685                          return False
    2686                  gmMeasurementWidgets.manage_measurements(parent = self, single_selection = True, emr = pat.emr)
    2687          #----------------------------------------------
    2688 -        def __on_show_emr_summary(self, event):
    2689                  pat = gmPerson.gmCurrentPatient()
    2690                  if not pat.connected:
    2691                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot show EMR summary. No active patient.'))
    2692                          return False
    2693
    2694                  emr = pat.get_emr()
    2695                  dlg = wx.MessageDialog (
    2696                          parent = self,
    2697                          message = emr.format_statistics(),
    2698                          caption = _('EMR Summary'),
    2699                          style = wx.OK | wx.STAY_ON_TOP
    2700                  )
    2701                  dlg.ShowModal()
    2702                  dlg.Destroy()
    2703                  return True
    2704          #----------------------------------------------
    2705 -        def __on_search_emr(self, event):
    2706                  return gmNarrativeWidgets.search_narrative_in_emr(parent=self)
    2707          #----------------------------------------------
    2708 -        def __on_search_across_emrs(self, event):
    2709                  gmNarrativeWidgets.search_narrative_across_emrs(parent=self)
    2710          #----------------------------------------------
    2711 -        def __on_export_emr_as_journal(self, event):
    2712                  # sanity checks
    2713                  pat = gmPerson.gmCurrentPatient()
    2714                  if not pat.connected:
    2715                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
    2716                          return False
    2717                  # get file name
    2718                  aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
    2719                  aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', pat['dirname']))
    2720                  gmTools.mkdir(aDefDir)
    2721                  # FIXME: make configurable
    2722                  fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
    2723                  dlg = wx.FileDialog (
    2724                          parent = self,
    2725                          message = _("Save patient's EMR journal as..."),
    2726                          defaultDir = aDefDir,
    2727                          defaultFile = fname,
    2728                          wildcard = aWildcard,
    2729                          style = wx.SAVE
    2730                  )
    2731                  choice = dlg.ShowModal()
    2732                  fname = dlg.GetPath()
    2733                  dlg.Destroy()
    2734                  if choice != wx.ID_OK:
    2735                          return True
    2736
    2737                  _log.debug('exporting EMR journal to [%s]' % fname)
    2738                  # instantiate exporter
    2739                  exporter = gmPatientExporter.cEMRJournalExporter()
    2740
    2741                  wx.BeginBusyCursor()
    2742                  try:
    2743                          fname = exporter.export_to_file(filename = fname)
    2744                  except:
    2745                          wx.EndBusyCursor()
    2746                          gmGuiHelpers.gm_show_error (
    2747                                  _('Error exporting patient EMR as chronological journal.'),
    2748                                  _('EMR journal export')
    2749                          )
    2750                          raise
    2751                  wx.EndBusyCursor()
    2752
    2753                  gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
    2754
    2755                  return True
    2756          #----------------------------------------------
    2757 -        def __on_export_for_medistar(self, event):
    2758                  gmNarrativeWidgets.export_narrative_for_medistar_import (
    2759                          parent = self,
    2760                          soap_cats = u'soapu',
    2761                          encounter = None                        # IOW, the current one
    2762                  )
    2763          #----------------------------------------------
    2764 -        def __on_add_tag2person(self, event):
    2765                  curr_pat = gmPerson.gmCurrentPatient()
    2766                  if not curr_pat.connected:
    2767                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
    2768                          return
    2769
    2770                  tag = gmDemographicsWidgets.manage_tag_images(parent = self)
    2771                  if tag is None:
    2772                          return
    2773
    2774                  tag = curr_pat.add_tag(tag['pk_tag_image'])
    2775                  msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
    2776                  comment = wx.GetTextFromUser (
    2777                          message = msg,
    2778                          caption = _('Editing tag comment'),
    2779                          default_value = gmTools.coalesce(tag['comment'], u''),
    2780                          parent = self
    2781                  )
    2782
    2783                  if comment == u'':
    2784                          return
    2785
    2786                  if comment.strip() == tag['comment']:
    2787                          return
    2788
    2789                  if comment == u' ':
    2790                          tag['comment'] = None
    2791                  else:
    2792                          tag['comment'] = comment.strip()
    2793
    2794                  tag.save()
    2795          #----------------------------------------------
    2796 -        def __on_load_external_patient(self, event):
    2797                  dbcfg = gmCfg.cCfgSQL()
    2798                  search_immediately = bool(dbcfg.get2 (
    2799                          option = 'patient_search.external_sources.immediately_search_if_single_source',
    2800                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    2801                          bias = 'user',
    2802                          default = 0
    2803                  ))
    2804                  gmPatSearchWidgets.get_person_from_external_sources(parent=self, search_immediately=search_immediately, activate_immediately=True)
    2805          #----------------------------------------------
    2806 -        def __on_export_as_gdt(self, event):
    2807                  curr_pat = gmPerson.gmCurrentPatient()
    2808                  if not curr_pat.connected:
    2809                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
    2810                          return False
    2811                  enc = 'cp850'                   # FIXME: configurable
    2812                  fname = os.path.expanduser(os.path.join('~', 'gnumed', 'current-patient.gdt'))
    2813                  curr_pat.export_as_gdt(filename = fname, encoding = enc)
    2814                  gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
    2815          #----------------------------------------------
    2816 -        def __on_search_person(self, evt):
    2817                  gmDispatcher.send(signal = u'focus_patient_search')
    2818          #----------------------------------------------
    2819 -        def __on_create_new_patient(self, evt):
    2820                  gmPersonCreationWidgets.create_new_person(parent = self, activate = True)
    2821          #----------------------------------------------
    2822 -        def __on_enlist_patient_as_staff(self, event):
    2823                  pat = gmPerson.gmCurrentPatient()
    2824                  if not pat.connected:
    2825                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot add staff member. No active patient.'))
    2826                          return False
    2827                  dlg = gmStaffWidgets.cAddPatientAsStaffDlg(parent=self, id=-1)
    2828                  dlg.ShowModal()
    2829          #----------------------------------------------
    2830 -        def __on_delete_patient(self, event):
    2831                  pat = gmPerson.gmCurrentPatient()
    2832                  if not pat.connected:
    2833                          gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete patient. No patient active.'))
    2834                          return False
    2835                  gmDemographicsWidgets.disable_identity(identity=pat)
    2836                  return True
    2837          #----------------------------------------------
    2838 -        def __on_merge_patients(self, event):
    2839                  gmPatSearchWidgets.merge_patients(parent=self)
    2840          #----------------------------------------------
    2841 -        def __on_add_new_staff(self, event):
    2842                  """Create new person and add it as staff."""
    2843                  if not gmPersonCreationWidgets.create_new_person(parent = self, activate = True):
    2844                          return
    2845                  dlg = gmStaffWidgets.cAddPatientAsStaffDlg(parent=self, id=-1)
    2846                  dlg.ShowModal()
    2847          #----------------------------------------------
    2848 -        def __on_edit_staff_list(self, event):
    2849                  dlg = gmStaffWidgets.cEditStaffListDlg(parent=self, id=-1)
    2850                  dlg.ShowModal()
    2851          #----------------------------------------------
    2852 -        def __on_edit_gmdbowner_password(self, evt):
    2853                  gmAuthWidgets.change_gmdbowner_password()
    2854          #----------------------------------------------
    2855 -        def __on_update_loinc(self, evt):
    2856                  gmMeasurementWidgets.update_loinc_reference_data()
    2857          #----------------------------------------------
    2858 -        def __on_update_atc(self, evt):
    2859                  gmMedicationWidgets.update_atc_reference_data()
    2860          #----------------------------------------------
    2861 -        def __on_install_data_packs(self, evt):
    2862                  gmDataPackWidgets.manage_data_packs(parent = self)
    2863          #----------------------------------------------
    2864 -        def __on_generate_vaccines(self, evt):
    2865                  wx.BeginBusyCursor()
    2866                  gmVaccination.regenerate_generic_vaccines()
    2867                  wx.EndBusyCursor()
    2868          #----------------------------------------------
    2869 -        def _clean_exit(self):
    2870                  """Cleanup helper.
    2871
    2872                  - should ALWAYS be called when this program is
    2873                    to be terminated
    2874                  - ANY code that should be executed before a
    2875                    regular shutdown should go in here
    2876                  - framework still functional
    2877                  """
    2878                  _log.debug('gmTopLevelFrame._clean_exit() start')
    2879
    2880                  # shut down backend notifications listener
    2881                  listener = gmBackendListener.gmBackendListener()
    2882                  try:
    2883                          listener.shutdown()
    2884                  except:
    2885                          _log.exception('cannot stop backend notifications listener thread')
    2886
    2887                  # shutdown application scripting listener
    2888                  if _scripting_listener is not None:
    2889                          try:
    2890                                  _scripting_listener.shutdown()
    2891                          except:
    2892                                  _log.exception('cannot stop scripting listener thread')
    2893
    2894                  # shutdown timers
    2895                  self.clock_update_timer.Stop()
    2896                  gmTimer.shutdown()
    2897                  gmPhraseWheel.shutdown()
    2898
    2899                  # run synchronous pre-exit callback
    2900                  for call_back in self.__pre_exit_callbacks:
    2901                          try:
    2902                                  call_back()
    2903                          except:
    2904                                  print "*** pre-exit callback failed ***"
    2905                                  print call_back
    2906                                  _log.exception('callback [%s] failed', call_back)
    2907
    2908                  # signal imminent demise to plugins
    2909                  gmDispatcher.send(u'application_closing')
    2910
    2911                  # do not show status line messages anymore
    2912                  gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
    2913
    2914                  # remember GUI size
    2915                  curr_width, curr_height = self.GetClientSizeTuple()
    2916                  _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
    2917                  dbcfg = gmCfg.cCfgSQL()
    2918                  dbcfg.set (
    2919                          option = 'main.window.width',
    2920                          value = curr_width,
    2921                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
    2922                  )
    2923                  dbcfg.set (
    2924                          option = 'main.window.height',
    2925                          value = curr_height,
    2926                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
    2927                  )
    2928
    2929                  if _cfg.get(option = 'debug'):
    2930                          print '---=== GNUmed shutdown ===---'
    2931                          try:
    2932                                  print _('You have to manually close this window to finalize shutting down GNUmed.')
    2933                                  print _('This is so that you can inspect the console output at your leisure.')
    2934                          except UnicodeEncodeError:
    2935                                  print 'You have to manually close this window to finalize shutting down GNUmed.'
    2936                                  print 'This is so that you can inspect the console output at your leisure.'
    2937                          print '---=== GNUmed shutdown ===---'
    2938
    2939                  # shutdown GUI exception handling
    2940                  gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
    2941
    2942                  # are we clean ?
    2943                  import threading
    2944                  _log.debug("%s active threads", threading.activeCount())
    2945                  for t in threading.enumerate():
    2946                          _log.debug('thread %s', t)
    2947
    2948                  _log.debug('gmTopLevelFrame._clean_exit() end')
    2949          #----------------------------------------------
    2950          # internal API
    2951          #----------------------------------------------
    2952 -        def __set_window_title_template(self):
    2953
    2954                  if _cfg.get(option = 'slave'):
    2955                          self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s in %%(site)s of %%(prax)s] (%s:%s)' % (
    2956                                  _cfg.get(option = 'slave personality'),
    2957                                  _cfg.get(option = 'xml-rpc port')
    2958                          )
    2959                  else:
    2960                          self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s in %(site)s of %(prax)s]'
    2961          #----------------------------------------------
    2962 -        def __update_window_title(self):
    2963                  """Update title of main window based on template.
    2964
    2965                  This gives nice tooltips on iconified GNUmed instances.
    2966
    2967                  User research indicates that in the title bar people want
    2968                  the date of birth, not the age, so please stick to this
    2969                  convention.
    2970                  """
    2971                  args = {}
    2972
    2973                  pat = gmPerson.gmCurrentPatient()
    2974                  if pat.connected:
    2975                          args['pat'] = u'%s %s %s (%s) #%d' % (
    2976                                  gmTools.coalesce(pat['title'], u'', u'%.4s'),
    2977                                  pat['firstnames'],
    2978                                  pat['lastnames'],
    2979                                  pat.get_formatted_dob(format = '%Y %b %d', encoding = gmI18N.get_encoding()),
    2980                                  pat['pk_identity']
    2981                          )
    2982                  else:
    2983                          args['pat'] = _('no patient')
    2984
    2985                  args['prov'] = u'%s%s.%s' % (
    2986                          gmTools.coalesce(_provider['title'], u'', u'%s '),
    2987                          _provider['firstnames'][:1],
    2988                          _provider['lastnames']
    2989                  )
    2990
    2991                  praxis = gmPraxis.gmCurrentPraxisBranch()
    2992                  args['wp'] = praxis.active_workplace
    2993                  args['site'] = praxis['branch']
    2994                  args['prax'] = praxis['praxis']
    2995
    2996                  self.SetTitle(self.__title_template % args)
    2997          #----------------------------------------------
    2998          #----------------------------------------------
    2999 -        def setup_statusbar(self):
    3000                  sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
    3001                  sb.SetStatusWidths([-1, 225])
    3002                  # add time and date display to the right corner of the status bar
    3003                  self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
    3004                  self._cb_update_clock()
    3005                  # update every second
    3006                  self.clock_update_timer.Start(milliseconds = 1000)
    3007          #----------------------------------------------
    3008 -        def _cb_update_clock(self):
    3009                  """Displays date and local time in the second slot of the status bar"""
    3010                  t = time.localtime(time.time())
    3011                  st = time.strftime('%Y %b %d  %H:%M:%S', t).decode(gmI18N.get_encoding(), 'replace')
    3012                  self.SetStatusText(st, 1)
    3013          #------------------------------------------------
    3014 -        def Lock(self):
    3015                  """Lock GNUmed client against unauthorized access"""
    3016                  # FIXME
    3017  #               for i in range(1, self.nb.GetPageCount()):
    3018  #                       self.nb.GetPage(i).Enable(False)
    3019                  return
    3020          #----------------------------------------------
    3021 -        def Unlock(self):
    3022                  """Unlock the main notebook widgets
    3023                  As long as we are not logged into the database backend,
    3024                  all pages but the 'login' page of the main notebook widget
    3025                  are locked; i.e. not accessible by the user
    3026                  """
    3027                  #unlock notebook pages
    3028  #               for i in range(1, self.nb.GetPageCount()):
    3029  #                       self.nb.GetPage(i).Enable(True)
    3030                  # go straight to patient selection
    3031  #               self.nb.AdvanceSelection()
    3032                  return
    3033          #-----------------------------------------------
    3034 -        def OnPanelSize (self, event):
    3035                  wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
    3036  #==============================================================================
    3037 -class gmApp(wx.App):
    3038
    3039 -        def OnInit(self):
    3040
    3041                  self.__starting_up = True
    3042
    3043                  gmExceptionHandlingWidgets.install_wx_exception_handler()
    3044                  gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
    3045
    3046                  # set this so things like "wx.StandardPaths.GetDataDir()" work as expected
    3047                  self.SetAppName(u'gnumed')
    3048                  self.SetVendorName(u'The GNUmed Development Community.')
    3049                  paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
    3050                  paths.init_paths(wx = wx, app_name = u'gnumed')
    3051
    3052                  if not self.__setup_prefs_file():
    3053                          return False
    3054
    3055                  gmExceptionHandlingWidgets.set_sender_email(gmPraxis.gmCurrentPraxisBranch().user_email)
    3056
    3057                  self.__guibroker = gmGuiBroker.GuiBroker()
    3058                  self.__setup_platform()
    3059
    3060                  if not self.__establish_backend_connection():
    3061                          return False
    3062                  if not self.__verify_db_account():
    3063                          return False
    3064                  if not self.__verify_praxis_branch():
    3065                          return False
    3066
    3067                  self.__check_db_lang()
    3068                  self.__update_workplace_list()
    3069
    3070                  if not _cfg.get(option = 'skip-update-check'):
    3071                          self.__check_for_updates()
    3072
    3073                  if _cfg.get(option = 'slave'):
    3074                          if not self.__setup_scripting_listener():
    3075                                  return False
    3076
    3077                  # FIXME: load last position from backend
    3078                  frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
    3079                  frame.CentreOnScreen(wx.BOTH)
    3080                  self.SetTopWindow(frame)
    3081                  frame.Show(True)
    3082
    3083                  if _cfg.get(option = 'debug'):
    3084                          self.RedirectStdio()
    3085                          self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
    3086                          # print this so people know what this window is for
    3087                          # and don't get suprised when it pops up later
    3088                          print '---=== GNUmed startup ===---'
    3089                          print _('redirecting STDOUT/STDERR to this log window')
    3090                          print '---=== GNUmed startup ===---'
    3091
    3092                  self.__setup_user_activity_timer()
    3093                  self.__register_events()
    3094
    3095                  wx.CallAfter(self._do_after_init)
    3096
    3097                  return True
    3098          #----------------------------------------------
    3099 -        def OnExit(self):
    3100                  """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
    3101
    3102                  - after destroying all application windows and controls
    3103                  - before wx.Windows internal cleanup
    3104                  """
    3105                  _log.debug('gmApp.OnExit() start')
    3106
    3107                  self.__shutdown_user_activity_timer()
    3108
    3109                  if _cfg.get(option = 'debug'):
    3110                          self.RestoreStdio()
    3111                          sys.stdin = sys.__stdin__
    3112                          sys.stdout = sys.__stdout__
    3113                          sys.stderr = sys.__stderr__
    3114
    3115                  _log.debug('gmApp.OnExit() end')
    3116          #----------------------------------------------
    3117 -        def _on_query_end_session(self, *args, **kwargs):
    3118                  wx.Bell()
    3119                  wx.Bell()
    3120                  wx.Bell()
    3121                  _log.warning('unhandled event detected: QUERY_END_SESSION')
    3122                  _log.info('we should be saving ourselves from here')
    3123                  gmLog2.flush()
    3124                  print "unhandled event detected: QUERY_END_SESSION"
    3125          #----------------------------------------------
    3126 -        def _on_end_session(self, *args, **kwargs):
    3127                  wx.Bell()
    3128                  wx.Bell()
    3129                  wx.Bell()
    3130                  _log.warning('unhandled event detected: END_SESSION')
    3131                  gmLog2.flush()
    3132                  print "unhandled event detected: END_SESSION"
    3133          #----------------------------------------------
    3134 -        def _on_app_activated(self, evt):
    3135                  if evt.GetActive():
    3136                          if self.__starting_up:
    3137                                  gmHooks.run_hook_script(hook = u'app_activated_startup')
    3138                          else:
    3139                                  gmHooks.run_hook_script(hook = u'app_activated')
    3140                  else:
    3141                          gmHooks.run_hook_script(hook = u'app_deactivated')
    3142
    3143                  evt.Skip()
    3144          #----------------------------------------------
    3145 -        def _on_user_activity(self, evt):
    3146                  self.user_activity_detected = True
    3147                  evt.Skip()
    3148          #----------------------------------------------
    3149 -        def _on_user_activity_timer_expired(self, cookie=None):
    3150
    3151                  if self.user_activity_detected:
    3152                          self.elapsed_inactivity_slices = 0
    3153                          self.user_activity_detected = False
    3154                          self.elapsed_inactivity_slices += 1
    3155                  else:
    3156                          if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
    3157  #                               print "User was inactive for 30 seconds."
    3158                                  pass
    3159
    3160                  self.user_activity_timer.Start(oneShot = True)
    3161          #----------------------------------------------
    3162          # internal helpers
    3163          #----------------------------------------------
    3164 -        def _signal_debugging_monitor(*args, **kwargs):
    3165                  try:
    3166                          kwargs['originated_in_database']
    3167                          print '==> got notification from database "%s":' % kwargs['signal']
    3168                  except KeyError:
    3169                          print '==> received signal from client: "%s"' % kwargs['signal']
    3170
    3171                  del kwargs['signal']
    3172                  for key in kwargs.keys():
    3173                          print '    [%s]: %s' % (key, kwargs[key])
    3174          #----------------------------------------------
    3175 -        def _do_after_init(self):
    3176                  self.__starting_up = False
    3177                  gmClinicalRecord.set_func_ask_user(a_func = gmEMRStructWidgets.ask_for_encounter_continuation)
    3178                  #gmPerson.set_emr_access_spinner(func = gmEMRStructWidgets.emr_access_spinner)
    3179                  self.__guibroker['horstspace.top_panel']._TCTRL_patient_selector.SetFocus()
    3180                  gmHooks.run_hook_script(hook = u'startup-after-GUI-init')
    3181          #----------------------------------------------
    3182 -        def __setup_user_activity_timer(self):
    3183                  self.user_activity_detected = True
    3184                  self.elapsed_inactivity_slices = 0
    3185                  # FIXME: make configurable
    3186                  self.max_user_inactivity_slices = 15    # 15 * 2000ms == 30 seconds
    3187                  self.user_activity_timer = gmTimer.cTimer (
    3188                          callback = self._on_user_activity_timer_expired,
    3189                          delay = 2000                    # hence a minimum of 2 and max of 3.999... seconds after which inactivity is detected
    3190                  )
    3191                  self.user_activity_timer.Start(oneShot=True)
    3192          #----------------------------------------------
    3193 -        def __shutdown_user_activity_timer(self):
    3194                  try:
    3195                          self.user_activity_timer.Stop()
    3196                          del self.user_activity_timer
    3197                  except:
    3198                          pass
    3199          #----------------------------------------------
    3200 -        def __register_events(self):
    3201                  wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
    3202                  wx.EVT_END_SESSION(self, self._on_end_session)
    3203
    3204                  # You can bind your app to wx.EVT_ACTIVATE_APP which will fire when your
    3205                  # app gets/looses focus, or you can wx.EVT_ACTIVATE with any of your
    3206                  # toplevel windows and call evt.GetActive() in the handler to see whether
    3207                  # it is gaining or loosing focus.
    3208                  self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
    3209
    3210                  self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
    3211                  self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
    3212
    3213                  if _cfg.get(option = 'debug'):
    3214                          gmDispatcher.connect(receiver = self._signal_debugging_monitor)
    3215                          _log.debug('connected signal monitor')
    3216          #----------------------------------------------
    3217 -        def __check_for_updates(self):
    3218
    3219                  dbcfg = gmCfg.cCfgSQL()
    3220
    3221                  do_check = bool(dbcfg.get2 (
    3222                          option = u'horstspace.update.autocheck_at_startup',
    3223                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    3224                          bias = 'workplace',
    3225                          default = True
    3226                  ))
    3227
    3228                  if not do_check:
    3229                          return
    3230
    3231                  gmCfgWidgets.check_for_updates()
    3232          #----------------------------------------------
    3233 -        def __establish_backend_connection(self):
    3234                  """Handle all the database related tasks necessary for startup."""
    3235                  override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
    3236                  from Gnumed.wxpython import gmAuthWidgets
    3237                  connected = gmAuthWidgets.connect_to_database (
    3238                          expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
    3239                          require_version = not override
    3240                  )
    3241                  if connected:
    3242                          return True
    3243                  _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
    3244                  return False
    3245          #----------------------------------------------
    3246 -        def __verify_db_account(self):
    3247                  # check account <-> staff member association
    3248                  global _provider
    3249                  try:
    3250                          _provider = gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
    3251                  except ValueError:
    3252                          account = gmPG2.get_current_user()
    3253                          _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
    3254                          msg = _(
    3255                                  'The database account [%s] cannot be used as a\n'
    3256                                  'staff member login for GNUmed. There was an\n'
    3257                                  'error retrieving staff details for it.\n\n'
    3258                                  'Please ask your administrator for help.\n'
    3259                          ) % account
    3260                          gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
    3261                          return False
    3262
    3263                  # improve exception handler setup
    3264                  tmp = '%s%s %s (%s = %s)' % (
    3265                          gmTools.coalesce(_provider['title'], ''),
    3266                          _provider['firstnames'],
    3267                          _provider['lastnames'],
    3268                          _provider['short_alias'],
    3269                          _provider['db_user']
    3270                  )
    3271                  gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
    3272
    3273                  return True
    3274          #----------------------------------------------
    3275 -        def __verify_praxis_branch(self):
    3276
    3277                  if not gmPraxisWidgets.set_active_praxis_branch(no_parent = True):
    3278                          return False
    3279
    3280                  login = gmPG2.get_default_login()
    3281                  msg = u'\n'
    3282                  msg += _('Database <%s> on <%s>') % (
    3283                          login.database,
    3284                          gmTools.coalesce(login.host, u'localhost')
    3285                  )
    3286                  msg += u'\n\n'
    3287
    3288                  praxis = gmPraxis.gmCurrentPraxisBranch()
    3289                  msg += _('Branch "%s" of praxis "%s"\n') % (
    3290                          praxis['branch'],
    3291                          praxis['praxis']
    3292                  )
    3293                  msg += u'\n\n'
    3294
    3295                  banner = praxis.db_logon_banner
    3296                  if banner.strip() == u'':
    3297                          return True
    3298                  msg += banner
    3299                  msg += u'\n\n'
    3300
    3301                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
    3302                          None,           #self.GetTopWindow(),                           # freezes
    3303                          -1,
    3304                          caption = _('Verifying database'),
    3305                          question = gmTools.wrap(msg, 60, initial_indent = u'    ', subsequent_indent = u'    '),
    3306                          button_defs = [
    3307                                  {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
    3308                                  {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
    3309                          ]
    3310                  )
    3311                  log_on = dlg.ShowModal()
    3312                  dlg.Destroy()
    3313                  if log_on == wx.ID_YES:
    3314                          return True
    3315                  _log.info('user decided to not connect to this database')
    3316                  return False
    3317          #----------------------------------------------
    3318 -        def __update_workplace_list(self):
    3319                  wps = gmPraxis.gmCurrentPraxisBranch().workplaces
    3320                  if len(wps) == 0:
    3321                          return
    3322                  login = gmPG2.get_default_login()
    3323                  prefs_file = _cfg.get(option = 'user_preferences_file')
    3324                  gmCfg2.set_option_in_INI_file (
    3325                          filename = prefs_file,
    3326                          group = u'profile %s' % login.backend_profile,
    3327                          option = u'last known workplaces',
    3328                          value = wps
    3329                  )
    3330                  _cfg.reload_file_source(file = prefs_file)
    3331          #----------------------------------------------
    3332 -        def __setup_prefs_file(self):
    3333                  """Setup access to a config file for storing preferences."""
    3334
    3335                  paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
    3336
    3337                  candidates = []
    3338                  explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
    3339                  if explicit_file is not None:
    3340                          candidates.append(explicit_file)
    3341                  # provide a few fallbacks in the event the --conf-file isn't writable
    3342                  candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
    3343                  candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
    3344                  candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
    3345
    3346                  prefs_file = None
    3347                  for candidate in candidates:
    3348                          try:
    3349                                  open(candidate, 'a+').close()
    3350                                  prefs_file = candidate
    3351                                  break
    3352                          except IOError:
    3353                                  continue
    3354
    3355                  if prefs_file is None:
    3356                          msg = _(
    3357                                  'Cannot find configuration file in any of:\n'
    3358                                  '\n'
    3359                                  ' %s\n'
    3360                                  'You may need to use the comand line option\n'
    3361                                  '\n'
    3362                                  '       --conf-file=<FILE>'
    3363                          ) % '\n '.join(candidates)
    3364                          gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
    3365                          return False
    3366
    3367                  _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
    3368                  _log.info('user preferences file: %s', prefs_file)
    3369
    3370                  return True
    3371          #----------------------------------------------
    3372 -        def __setup_scripting_listener(self):
    3373
    3374                  from socket import error as SocketError
    3375                  from Gnumed.pycommon import gmScriptingListener
    3376                  from Gnumed.wxpython import gmMacro
    3377
    3378                  slave_personality = gmTools.coalesce (
    3379                          _cfg.get (
    3380                                  group = u'workplace',
    3381                                  option = u'slave personality',
    3382                                  source_order = [
    3383                                          ('explicit', 'return'),
    3384                                          ('workbase', 'return'),
    3385                                          ('user', 'return'),
    3386                                          ('system', 'return')
    3387                                  ]
    3388                          ),
    3389                          u'gnumed-client'
    3390                  )
    3391                  _cfg.set_option(option = 'slave personality', value = slave_personality)
    3392
    3393                  # FIXME: handle port via /var/run/
    3394                  port = int (
    3395                          gmTools.coalesce (
    3396                                  _cfg.get (
    3397                                          group = u'workplace',
    3398                                          option = u'xml-rpc port',
    3399                                          source_order = [
    3400                                                  ('explicit', 'return'),
    3401                                                  ('workbase', 'return'),
    3402                                                  ('user', 'return'),
    3403                                                  ('system', 'return')
    3404                                          ]
    3405                                  ),
    3406                                  9999
    3407                          )
    3408                  )
    3409                  _cfg.set_option(option = 'xml-rpc port', value = port)
    3410
    3411                  macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
    3412                  global _scripting_listener
    3413                  try:
    3414                          _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
    3415                  except SocketError, e:
    3416                          _log.exception('cannot start GNUmed XML-RPC server')
    3417                          gmGuiHelpers.gm_show_error (
    3418                                  aMessage = (
    3419                                          'Cannot start the GNUmed server:\n'
    3420                                          '\n'
    3421                                          ' [%s]'
    3422                                  ) % e,
    3423                                  aTitle = _('GNUmed startup')
    3424                          )
    3425                          return False
    3426
    3427                  return True
    3428          #----------------------------------------------
    3429 -        def __setup_platform(self):
    3430
    3431                  import wx.lib.colourdb
    3432                  wx.lib.colourdb.updateColourDB()
    3433
    3434                  traits = self.GetTraits()
    3435                  try:
    3436                          _log.info('desktop environment: [%s]', traits.GetDesktopEnvironment())
    3437                  except:
    3438                          pass
    3439
    3440                  if wx.Platform == '__WXMSW__':
    3441                          _log.info('running on MS Windows')
    3442                  elif wx.Platform == '__WXGTK__':
    3443                          _log.info('running on GTK (probably Linux)')
    3444                  elif wx.Platform == '__WXMAC__':
    3445                          _log.info('running on Mac OS')
    3446                          wx.SystemOptions.SetOptionInt('mac.textcontrol-use-spell-checker', 1)
    3447                  else:
    3448                          _log.info('running on an unknown platform (%s)' % wx.Platform)
    3449          #----------------------------------------------
    3450 -        def __check_db_lang(self):
    3451                  if gmI18N.system_locale is None or gmI18N.system_locale == '':
    3452                          _log.warning("system locale is undefined (probably meaning 'C')")
    3453                          return True
    3454
    3455                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
    3456                  curr_db_lang = rows[0]['lang']
    3457                  _log.debug("current database locale: [%s]" % curr_db_lang)
    3458
    3459                  if curr_db_lang is None:
    3460                          # try setting (only possible if translation exists)
    3461                          cmd = u'select i18n.set_curr_lang(%s)'
    3462                          for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
    3463                                  if len(lang) == 0:
    3464                                          continue
    3465                                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
    3466                                  if rows[0][0]:
    3467                                          _log.debug("Successfully set database language to [%s]." % lang)
    3468                                          return True
    3469                                  _log.error('Cannot set database language to [%s].' % lang)
    3470
    3471                          return True
    3472
    3473                  if curr_db_lang == gmI18N.system_locale_level['full']:
    3474                          _log.debug('Database locale (%s) up to date.' % curr_db_lang)
    3475                          return True
    3476                  if curr_db_lang == gmI18N.system_locale_level['country']:
    3477                          _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (curr_db_lang, gmI18N.system_locale))
    3478                          return True
    3479                  if curr_db_lang == gmI18N.system_locale_level['language']:
    3480                          _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (curr_db_lang, gmI18N.system_locale))
    3481                          return True
    3482
    3483                  _log.warning('database locale [%s] does not match system locale [%s]' % (curr_db_lang, gmI18N.system_locale))
    3484
    3485                  sys_lang2ignore = _cfg.get (
    3486                          group = u'backend',
    3487                          option = u'ignored mismatching system locale',
    3488                          source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
    3489                  )
    3490                  if gmI18N.system_locale == sys_lang2ignore:
    3491                          _log.info('configured to ignore system-to-database locale mismatch')
    3492                          return True
    3493
    3494                  # no match, not ignoring
    3495                  msg = _(
    3496                          "The currently selected database language ('%s') does\n"
    3497                          "not match the current system language ('%s').\n"
    3498                          "\n"
    3499                          "Do you want to set the database language to '%s' ?\n"
    3500                  ) % (curr_db_lang, gmI18N.system_locale, gmI18N.system_locale)
    3501                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
    3502                          None,
    3503                          -1,
    3504                          caption = _('Checking database language settings'),
    3505                          question = msg,
    3506                          button_defs = [
    3507                                  {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
    3508                                  {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
    3509                          ],
    3510                          show_checkbox = True,
    3511                          checkbox_msg = _('Remember to ignore language mismatch'),
    3512                          checkbox_tooltip = _(
    3513                                  'Checking this will make GNUmed remember your decision\n'
    3514                                  'until the system language is changed.\n'
    3515                                  '\n'
    3516                                  'You can also reactivate this inquiry by removing the\n'
    3517                                  'corresponding "ignore" option from the configuration file\n'
    3518                                  '\n'
    3519                                  ' [%s]'
    3520                          ) % _cfg.get(option = 'user_preferences_file')
    3521                  )
    3522                  decision = dlg.ShowModal()
    3523                  remember2ignore_this_mismatch = dlg._CHBOX_dont_ask_again.GetValue()
    3524                  dlg.Destroy()
    3525
    3526                  if decision == wx.ID_NO:
    3527                          if not remember2ignore_this_mismatch:
    3528                                  return True
    3529                          _log.info('User did not want to set database locale. Ignoring mismatch next time.')
    3530                          gmCfg2.set_option_in_INI_file (
    3531                                  filename = _cfg.get(option = 'user_preferences_file'),
    3532                                  group = 'backend',
    3533                                  option = 'ignored mismatching system locale',
    3534                                  value = gmI18N.system_locale
    3535                          )
    3536                          return True
    3537
    3538                  # try setting database language (only possible if translation exists)
    3539                  cmd = u'select i18n.set_curr_lang(%s)'
    3540                  for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
    3541                          if len(lang) == 0:
    3542                                  continue
    3543                          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
    3544                          if rows[0][0]:
    3545                                  _log.debug("Successfully set database language to [%s]." % lang)
    3546                                  return True
    3547                          _log.error('Cannot set database language to [%s].' % lang)
    3548
    3549                  # no match found but user wanted to set language anyways, so force it
    3550                  _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
    3551                  gmPG2.run_rw_queries(queries = [{
    3552                          'cmd': u'select i18n.force_curr_lang(%s)',
    3553                          'args': [gmI18N.system_locale_level['country']]
    3554                  }])
    3555
    3556                  return True
    3557  #==============================================================================
    3558 -def _signal_debugging_monitor(*args, **kwargs):
    3559          try:
    3560                  kwargs['originated_in_database']
    3561                  print '==> got notification from database "%s":' % kwargs['signal']
    3562          except KeyError:
    3563                  print '==> received signal from client: "%s"' % kwargs['signal']
    3564
    3565          del kwargs['signal']
    3566          for key in kwargs.keys():
    3567                  # careful because of possibly limited console output encoding
    3568                  try: print '    [%s]: %s' % (key, kwargs[key])
    3569                  except: print 'cannot print signal information'
    3570  #==============================================================================
    3571 -def _safe_wxEndBusyCursor():
    3572          try: _original_wxEndBusyCursor()
    3573          except wx.PyAssertionError: pass
    3574  #------------------------------------------------------------------------------
    3575 -def setup_safe_wxEndBusyCursor():
    3576          # monkey patch wxPython, needed on Windows ...
    3577          if os.name != 'nt':
    3578                  return
    3579          print "GNUmed startup: Monkey patching wx.EndBusyCursor..."
    3580          global _original_wxEndBusyCursor
    3581          _original_wxEndBusyCursor = wx.EndBusyCursor
    3582          wx.EndBusyCursor = _safe_wxEndBusyCursor
    3583          _log.debug('monkey patched wx.EndBusyCursor:')
    3584          _log.debug('[%s] -> [%s]', _original_wxEndBusyCursor, _safe_wxEndBusyCursor)
    3585  #==============================================================================
    3586 -def main():
    3587
    3588          if _cfg.get(option = 'debug'):
    3589                  gmDispatcher.connect(receiver = _signal_debugging_monitor)
    3590                  _log.debug('gmDispatcher signal monitor activated')
    3591
    3592          setup_safe_wxEndBusyCursor()
    3593
    3594          wx.InitAllImageHandlers()
    3595          # create an instance of our GNUmed main application
    3596          # - do not redirect stdio (yet)
    3597          # - allow signals to be delivered
    3598          app = gmApp(redirect = False, clearSigInt = False)
    3599          app.MainLoop()
    3600  #==============================================================================
    3601  # Main
    3602  #==============================================================================
    3603  if __name__ == '__main__':
    3604
    3605          from GNUmed.pycommon import gmI18N
    3606          gmI18N.activate_locale()
    3607          gmI18N.install_domain()
    3608
    3609          _log.info('Starting up as main module.')
    3610          main()
    3611
    3612  #==============================================================================
    3613