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

# Source Code for Module Gnumed.wxpython.gmGuiMain

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