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