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