Gnumed.wxpython.gmGuiMain

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