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