Gnumed.wxpython.gmGuiMain

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