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 #----------------------------------------------
304 - def __setup_main_menu(self):
305 """Create the main menu entries. 306 307 Individual entries are farmed out to the modules. 308 309 menu item template: 310 311 item = menu_*.Append(-1) 312 self.Bind(wx.EVT_MENU, self.__on_*, item) 313 """ 314 global wx 315 self.mainmenu = wx.MenuBar() 316 self.__gb['main.mainmenu'] = self.mainmenu 317 318 # -- menu "GNUmed" ----------------- 319 menu_gnumed = wx.Menu() 320 self.menu_plugins = wx.Menu() 321 menu_gnumed.Append(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins) 322 item = menu_gnumed.Append(-1, _('Check for updates'), _('Check for new releases of the GNUmed client.')) 323 self.Bind(wx.EVT_MENU, self.__on_check_for_updates, item) 324 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.')) 325 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item) 326 menu_gnumed.AppendSeparator() 327 328 # GNUmed / Preferences 329 menu_config = wx.Menu() 330 331 item = menu_config.Append(-1, _('All options'), _('List all options as configured in the database.')) 332 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item) 333 334 # GNUmed / Preferences / Database 335 menu_cfg_db = wx.Menu() 336 item = menu_cfg_db.Append(-1, _('Language'), _('Configure the database language')) 337 self.Bind(wx.EVT_MENU, self.__on_configure_db_lang, item) 338 item = menu_cfg_db.Append(-1, _('Welcome message'), _('Configure the database welcome message (all users).')) 339 self.Bind(wx.EVT_MENU, self.__on_configure_db_welcome, item) 340 menu_config.Append(wx.NewId(), _('Database ...'), menu_cfg_db) 341 342 # GNUmed / Preferences / Client 343 menu_cfg_client = wx.Menu() 344 item = menu_cfg_client.Append(-1, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.')) 345 self.Bind(wx.EVT_MENU, self.__on_configure_export_chunk_size, item) 346 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.')) 347 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item) 348 menu_config.Append(wx.NewId(), _('Client parameters ...'), menu_cfg_client) 349 350 # GNUmed / Preferences / UI 351 menu_cfg_ui = wx.Menu() 352 item = menu_cfg_ui.Append(-1, _('Medication measurements'), _('Select the measurements panel to show in the medications plugin.')) 353 self.Bind(wx.EVT_MENU, self.__on_cfg_meds_lab_pnl, item) 354 item = menu_cfg_ui.Append(-1, _('General measurements'), _('Select the measurements panel to show in the top pane.')) 355 self.Bind(wx.EVT_MENU, self.__on_cfg_top_lab_pnl, item) 356 357 # gnumed / config / ui / docs 358 menu_cfg_doc = wx.Menu() 359 item = menu_cfg_doc.Append(-1, _('Review dialog'), _('Configure review dialog after document display.')) 360 self.Bind(wx.EVT_MENU, self.__on_configure_doc_review_dialog, item) 361 item = menu_cfg_doc.Append(-1, _('UUID display'), _('Configure unique ID dialog on document import.')) 362 self.Bind(wx.EVT_MENU, self.__on_configure_doc_uuid_dialog, item) 363 item = menu_cfg_doc.Append(-1, _('Empty documents'), _('Whether to allow saving documents without parts.')) 364 self.Bind(wx.EVT_MENU, self.__on_configure_partless_docs, item) 365 item = menu_cfg_doc.Append(-1, _('Generate UUID'), _('Whether to generate UUIDs for new documents.')) 366 self.Bind(wx.EVT_MENU, self.__on_configure_generate_doc_uuid, item) 367 menu_cfg_ui.Append(wx.NewId(), _('Document handling ...'), menu_cfg_doc) 368 369 # gnumed / config / ui / updates 370 menu_cfg_update = wx.Menu() 371 item = menu_cfg_update.Append(-1, _('Auto-check'), _('Whether to auto-check for updates at startup.')) 372 self.Bind(wx.EVT_MENU, self.__on_configure_update_check, item) 373 item = menu_cfg_update.Append(-1, _('Check scope'), _('When checking for updates, consider latest branch, too ?')) 374 self.Bind(wx.EVT_MENU, self.__on_configure_update_check_scope, item) 375 item = menu_cfg_update.Append(-1, _('URL'), _('The URL to retrieve version information from.')) 376 self.Bind(wx.EVT_MENU, self.__on_configure_update_url, item) 377 menu_cfg_ui.Append(wx.NewId(), _('Update handling ...'), menu_cfg_update) 378 379 # gnumed / config / ui / patient 380 menu_cfg_pat_search = wx.Menu() 381 item = menu_cfg_pat_search.Append(-1, _('Birthday reminder'), _('Configure birthday reminder proximity interval.')) 382 self.Bind(wx.EVT_MENU, self.__on_configure_dob_reminder_proximity, item) 383 item = menu_cfg_pat_search.Append(-1, _('Immediate source activation'), _('Configure immediate activation of single external person.')) 384 self.Bind(wx.EVT_MENU, self.__on_configure_quick_pat_search, item) 385 item = menu_cfg_pat_search.Append(-1, _('Initial plugin'), _('Configure which plugin to show right after person activation.')) 386 self.Bind(wx.EVT_MENU, self.__on_configure_initial_pat_plugin, item) 387 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default region for person creation.')) 388 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item) 389 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.')) 390 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item) 391 menu_cfg_ui.Append(wx.NewId(), _('Person ...'), menu_cfg_pat_search) 392 393 # gnumed / config / ui / soap handling 394 menu_cfg_soap_editing = wx.Menu() 395 item = menu_cfg_soap_editing.Append(-1, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.')) 396 self.Bind(wx.EVT_MENU, self.__on_allow_multiple_new_episodes, item) 397 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.')) 398 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item) 399 item = menu_cfg_soap_editing.Append(-1, _('SOAP fields'), _('Configure SOAP editor - individual SOAP fields vs text editor like')) 400 self.Bind(wx.EVT_MENU, self.__on_use_fields_in_soap_editor, item) 401 menu_cfg_ui.Append(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing) 402 403 # gnumed / config / External tools 404 menu_cfg_ext_tools = wx.Menu() 405 # item = menu_cfg_ext_tools.Append(-1, _('IFAP command'), _('Set the command to start IFAP.')) 406 # self.Bind(wx.EVT_MENU, self.__on_configure_ifap_cmd, item) 407 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.')) 408 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item) 409 item = menu_cfg_ext_tools.Append(-1, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.')) 410 self.Bind(wx.EVT_MENU, self.__on_configure_ooo_settle_time, item) 411 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.')) 412 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item) 413 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.')) 414 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item) 415 # item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.')) 416 # self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item) 417 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.')) 418 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item) 419 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.')) 420 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item) 421 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.')) 422 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item) 423 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.')) 424 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item) 425 menu_config.Append(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools) 426 427 # gnumed / config / billing 428 menu_cfg_bill = wx.Menu() 429 item = menu_cfg_bill.Append(-1, _('Invoice ID template'), _('Set the template for generating invoice IDs.')) 430 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_id_template, item) 431 item = menu_cfg_bill.Append(-1, _('Invoice template (no VAT)'), _('Select the template for printing an invoice without VAT.')) 432 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_template_no_vat, item) 433 item = menu_cfg_bill.Append(-1, _('Invoice template (with VAT)'), _('Select the template for printing an invoice with VAT.')) 434 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_template_with_vat, item) 435 item = menu_cfg_bill.Append(-1, _('Catalogs URL'), _('URL for billing catalogs (schedules of fees).')) 436 self.Bind(wx.EVT_MENU, self.__on_configure_billing_catalogs_url, item) 437 438 # gnumed / config / emr 439 menu_cfg_emr = wx.Menu() 440 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.')) 441 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item) 442 item = menu_cfg_emr.Append(-1, _('Prescription mode'), _('Select the default mode for creating a prescription.')) 443 self.Bind(wx.EVT_MENU, self.__on_cfg_prescription_mode, item) 444 item = menu_cfg_emr.Append(-1, _('Prescription template'), _('Select the template for printing a prescription.')) 445 self.Bind(wx.EVT_MENU, self.__on_cfg_prescription_template, item) 446 item = menu_cfg_emr.Append(-1, _('Default Gnuplot template'), _('Select the default template for plotting test results.')) 447 self.Bind(wx.EVT_MENU, self.__on_cfg_default_gnuplot_template, item) 448 item = menu_cfg_emr.Append(-1, _('Fallback provider'), _('Select the doctor to fall back to for patients without a primary provider.')) 449 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item) 450 451 # gnumed / config / emr / encounter 452 menu_cfg_encounter = wx.Menu() 453 item = menu_cfg_encounter.Append(-1, _('Edit before patient change'), _('Edit encounter details before change of patient.')) 454 self.Bind(wx.EVT_MENU, self.__on_cfg_enc_pat_change, item) 455 item = menu_cfg_encounter.Append(-1, _('Minimum duration'), _('Minimum duration of an encounter.')) 456 self.Bind(wx.EVT_MENU, self.__on_cfg_enc_min_ttl, item) 457 item = menu_cfg_encounter.Append(-1, _('Maximum duration'), _('Maximum duration of an encounter.')) 458 self.Bind(wx.EVT_MENU, self.__on_cfg_enc_max_ttl, item) 459 item = menu_cfg_encounter.Append(-1, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.')) 460 self.Bind(wx.EVT_MENU, self.__on_cfg_enc_empty_ttl, item) 461 item = menu_cfg_encounter.Append(-1, _('Default type'), _('Default type for new encounters.')) 462 self.Bind(wx.EVT_MENU, self.__on_cfg_enc_default_type, item) 463 menu_cfg_emr.Append(wx.NewId(), _('Encounter ...'), menu_cfg_encounter) 464 465 # gnumed / config / emr / episode 466 menu_cfg_episode = wx.Menu() 467 item = menu_cfg_episode.Append(-1, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.')) 468 self.Bind(wx.EVT_MENU, self.__on_cfg_epi_ttl, item) 469 menu_cfg_emr.Append(wx.NewId(), _('Episode ...'), menu_cfg_episode) 470 471 menu_config.Append(wx.NewId(), _('User interface ...'), menu_cfg_ui) 472 menu_config.Append(wx.NewId(), _('EMR ...'), menu_cfg_emr) 473 menu_config.Append(wx.NewId(), _('Billing ...'), menu_cfg_bill) 474 menu_gnumed.Append(wx.NewId(), _('Preferences ...'), menu_config) 475 476 # gnumed / master data 477 menu_master_data = wx.Menu() 478 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.')) 479 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item) 480 item = menu_master_data.Append(-1, _('Manage praxis'), _('Manage your praxis branches.')) 481 self.Bind(wx.EVT_MENU, self.__on_manage_praxis, item) 482 item = menu_master_data.Append(-1, _('Install data packs'), _('Install reference data from data packs.')) 483 self.Bind(wx.EVT_MENU, self.__on_install_data_packs, item) 484 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.')) 485 self.Bind(wx.EVT_MENU, self.__on_update_atc, item) 486 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.')) 487 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item) 488 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.')) 489 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item) 490 menu_gnumed.Append(wx.NewId(), _('&Master data ...'), menu_master_data) 491 492 # gnumed / users 493 menu_users = wx.Menu() 494 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user')) 495 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item) 496 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users')) 497 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item) 498 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner')) 499 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item) 500 menu_gnumed.Append(wx.NewId(), _('&Users ...'), menu_users) 501 502 menu_gnumed.AppendSeparator() 503 504 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.')) 505 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item) 506 507 self.mainmenu.Append(menu_gnumed, '&GNUmed') 508 509 # -- menu "Person" --------------------------- 510 menu_person = wx.Menu() 511 512 item = menu_person.Append(-1, _('Search'), _('Search for a person.')) 513 self.Bind(wx.EVT_MENU, self.__on_search_person, item) 514 acc_tab = wx.AcceleratorTable([(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, item.GetId())]) 515 self.SetAcceleratorTable(acc_tab) 516 item = menu_person.Append(-1, _('&Register person'), _("Register a new person with GNUmed")) 517 self.Bind(wx.EVT_MENU, self.__on_create_new_patient, item) 518 519 menu_person_import = wx.Menu() 520 item = menu_person_import.Append(-1, _('From &External sources'), _('Load and possibly create person from available external sources.')) 521 self.Bind(wx.EVT_MENU, self.__on_load_external_patient, item) 522 item = menu_person_import.Append(-1, _('&vCard file \u2192 patient'), _('Import demographics from .vcf vCard file as patient')) 523 self.Bind(wx.EVT_MENU, self.__on_import_vcard_from_file, item) 524 item = menu_person_import.Append(-1, _('Clipboard (&XML) \u2192 patient'), _('Import demographics from clipboard (LinuxMedNews XML) as patient')) 525 self.Bind(wx.EVT_MENU, self.__on_import_xml_linuxmednews, item) 526 item = menu_person_import.Append(-1, _('Clipboard (&vCard) \u2192 patient'), _('Import demographics from clipboard (vCard) as patient')) 527 self.Bind(wx.EVT_MENU, self.__on_import_vcard_from_clipboard, item) 528 menu_person.Append(wx.NewId(), '&Import\u2026', menu_person_import) 529 530 menu_person_export = wx.Menu() 531 menu_person_export_clipboard = wx.Menu() 532 item = menu_person_export_clipboard.Append(-1, '&GDT', _('Export demographics of currently active person as GDT into clipboard.')) 533 self.Bind(wx.EVT_MENU, self.__on_export_gdt2clipboard, item) 534 item = menu_person_export_clipboard.Append(-1, '&XML (LinuxMedNews)', _('Export demographics of currently active person as XML (LinuxMedNews) into clipboard')) 535 self.Bind(wx.EVT_MENU, self.__on_export_linuxmednews_xml2clipboard, item) 536 item = menu_person_export_clipboard.Append(-1, '&vCard', _('Export demographics of currently active person as vCard into clipboard')) 537 self.Bind(wx.EVT_MENU, self.__on_export_vcard2clipboard, item) 538 menu_person_export.Append(wx.NewId(), _('\u2192 &Clipboard as\u2026'), menu_person_export_clipboard) 539 540 menu_person_export_file = wx.Menu() 541 item = menu_person_export_file.Append(-1, '&GDT', _('Export demographics of currently active person into GDT file.')) 542 self.Bind(wx.EVT_MENU, self.__on_export_as_gdt, item) 543 item = menu_person_export_file.Append(-1, '&vCard', _('Export demographics of currently active person into vCard file.')) 544 self.Bind(wx.EVT_MENU, self.__on_export_as_vcard, item) 545 menu_person_export.Append(wx.NewId(), _('\u2192 &File as\u2026'), menu_person_export_file) 546 547 menu_person.Append(wx.NewId(), 'E&xport\u2026', menu_person_export) 548 549 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.')) 550 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item) 551 item = menu_person.Append(-1, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.')) 552 self.Bind(wx.EVT_MENU, self.__on_delete_patient, item) 553 menu_person.AppendSeparator() 554 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.')) 555 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item) 556 item = menu_person.Append(-1, _('Enlist as user'), _('Enlist current person as GNUmed user')) 557 self.Bind(wx.EVT_MENU, self.__on_enlist_patient_as_staff, item) 558 menu_person.AppendSeparator() 559 560 self.mainmenu.Append(menu_person, '&Person') 561 self.__gb['main.patientmenu'] = menu_person 562 563 # -- menu "EMR" --------------------------- 564 menu_emr = wx.Menu() 565 566 # -- EMR / Manage 567 menu_emr_manage = wx.Menu() 568 item = menu_emr_manage.Append(-1, _('&Past history (health issue / PMH)'), _('Add a past/previous medical history item (health issue) to the EMR of the active patient')) 569 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item) 570 item = menu_emr_manage.Append(-1, _('&Episode'), _('Add an episode of illness to the EMR of the active patient')) 571 self.Bind(wx.EVT_MENU, self.__on_add_episode, item) 572 item = menu_emr_manage.Append(-1, _('&Medication'), _('Add medication / substance use entry.')) 573 self.Bind(wx.EVT_MENU, self.__on_add_medication, item) 574 item = menu_emr_manage.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.')) 575 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item) 576 item = menu_emr_manage.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.')) 577 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item) 578 item = menu_emr_manage.Append(-1, _('&Hospitalizations'), _('Manage hospitalizations.')) 579 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item) 580 item = menu_emr_manage.Append(-1, _('&External care'), _('Manage external care.')) 581 self.Bind(wx.EVT_MENU, self.__on_manage_external_care, item) 582 item = menu_emr_manage.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.')) 583 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item) 584 item = menu_emr_manage.Append(-1, _('&Measurements'), _('Manage measurement results for the current patient.')) 585 self.Bind(wx.EVT_MENU, self.__on_manage_measurements, item) 586 item = menu_emr_manage.Append(-1, _('&Vaccinations: by shot'), _('Manage vaccinations for the current patient (by shots given).')) 587 self.Bind(wx.EVT_MENU, self.__on_manage_vaccination, item) 588 item = menu_emr_manage.Append(-1, _('&Vaccinations: by indication'), _('Manage vaccinations for the current patient (by indication).')) 589 self.Bind(wx.EVT_MENU, self.__on_show_all_vaccinations_by_indication, item) 590 item = menu_emr_manage.Append(-1, _('&Vaccinations: latest'), _('List latest vaccinations for the current patient.')) 591 self.Bind(wx.EVT_MENU, self.__on_show_latest_vaccinations, item) 592 item = menu_emr_manage.Append(-1, _('&Family history (FHx)'), _('Manage family history.')) 593 self.Bind(wx.EVT_MENU, self.__on_manage_fhx, item) 594 item = menu_emr_manage.Append(-1, _('&Encounters'), _('List all encounters including empty ones.')) 595 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item) 596 item = menu_emr_manage.Append(-1, _('&Pregnancy'), _('Calculate EDC.')) 597 self.Bind(wx.EVT_MENU, self.__on_calc_edc, item) 598 item = menu_emr_manage.Append(-1, _('Suppressed hints'), _('Manage dynamic hints suppressed in this patient.')) 599 self.Bind(wx.EVT_MENU, self.__on_manage_suppressed_hints, item) 600 item = menu_emr_manage.Append(-1, _('Substance abuse'), _('Manage substance abuse documentation of this patient.')) 601 self.Bind(wx.EVT_MENU, self.__on_manage_substance_abuse, item) 602 menu_emr.Append(wx.NewId(), _('&Manage ...'), menu_emr_manage) 603 604 # - EMR / 605 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient')) 606 self.Bind(wx.EVT_MENU, self.__on_search_emr, item) 607 608 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.')) 609 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item) 610 611 # # - EMR / Show as / 612 # menu_emr_show = wx.Menu() 613 614 item = menu_emr.Append(-1, _('Statistics'), _('Show a high-level statistic summary of the EMR.')) 615 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item) 616 617 # menu_emr.Append(wx.NewId(), _('Show as ...'), menu_emr_show) 618 # self.__gb['main.emr_showmenu'] = menu_emr_show 619 620 menu_emr.AppendSeparator() 621 622 # -- EMR / Export as 623 menu_emr_export = wx.Menu() 624 item = menu_emr_export.Append(-1, _('Journal (encounters)'), _("Copy EMR of the active patient as a chronological journal into export area")) 625 self.Bind(wx.EVT_MENU, self.__on_export_emr_as_journal, item) 626 item = menu_emr_export.Append(-1, _('Journal (mod time)'), _("Copy EMR of active patient as journal (by last modification time) into export area")) 627 self.Bind(wx.EVT_MENU, self.__on_export_emr_by_last_mod, item) 628 item = menu_emr_export.Append(-1, _('Text document'), _("Copy EMR of active patient as text document into export area")) 629 self.Bind(wx.EVT_MENU, self.__export_emr_as_textfile, item) 630 item = menu_emr_export.Append(-1, _('Timeline file'), _("Copy EMR of active patient as timeline file (XML) into export area")) 631 self.Bind(wx.EVT_MENU, self.__export_emr_as_timeline_xml, item) 632 item = menu_emr_export.Append(-1, _('Care structure'), _("Copy EMR of active patient as care structure text file into export area")) 633 self.Bind(wx.EVT_MENU, self.__export_emr_as_care_structure, item) 634 # structure file 635 item = menu_emr_export.Append(-1, _('MEDISTAR import format (as file)'), _("GNUmed -> MEDISTAR. Save progress notes of active patient's active encounter into a text file.")) 636 self.Bind(wx.EVT_MENU, self.__on_export_for_medistar, item) 637 menu_emr.Append(wx.NewId(), _('Put into export area as ...'), menu_emr_export) 638 639 menu_emr.AppendSeparator() 640 641 self.mainmenu.Append(menu_emr, _("&EMR")) 642 self.__gb['main.emrmenu'] = menu_emr 643 644 # -- menu "Paperwork" --------------------- 645 menu_paperwork = wx.Menu() 646 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.')) 647 self.Bind(wx.EVT_MENU, self.__on_new_letter, item) 648 item = menu_paperwork.Append(-1, _('Screenshot -> export area'), _('Put a screenshot into the patient export area.')) 649 self.Bind(wx.EVT_MENU, self.__on_save_screenshot_into_export_area, item) 650 menu_paperwork.AppendSeparator() 651 item = menu_paperwork.Append(-1, _('List Placeholders'), _('Show a list of all placeholders.')) 652 self.Bind(wx.EVT_MENU, self.__on_show_placeholders, item) 653 # item = menu_paperwork.Append(-1, _('Select receiver'), _('Select a letter receiver for testing.')) 654 # self.Bind(wx.EVT_MENU, self.__on_test_receiver_selection, item) 655 self.mainmenu.Append(menu_paperwork, _('&Correspondence')) 656 self.__gb['main.paperworkmenu'] = menu_paperwork 657 658 # -- menu "Tools" ------------------------- 659 self.menu_tools = wx.Menu() 660 item = self.menu_tools.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients')) 661 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item) 662 viewer = _('no viewer installed') 663 if gmShellAPI.detect_external_binary(binary = 'ginkgocadx')[0]: 664 viewer = 'Ginkgo CADx' 665 elif os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK): 666 viewer = 'OsiriX' 667 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]: 668 viewer = 'Aeskulap' 669 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]: 670 viewer = 'AMIDE' 671 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]: 672 viewer = 'DicomScope' 673 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]: 674 viewer = '(x)medcon' 675 item = self.menu_tools.Append(-1, _('DICOM viewer'), _('Start DICOM viewer (%s) for CD-ROM (X-Ray, CT, MR, etc). On Windows just insert CD.') % viewer) 676 self.Bind(wx.EVT_MENU, self.__on_dicom_viewer, item) 677 if viewer == _('no viewer installed'): 678 _log.info('neither of Ginkgo CADx / OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item') 679 self.menu_tools.Enable(id = item.Id, enable=False) 680 # self.menu_tools.Append(-1, _("Dermatology"), _("A tool to aid dermatology diagnosis")) 681 # self.Bind(wx.EVT_MENU, self.__dermtool, item) 682 item = self.menu_tools.Append(-1, _('Snellen chart'), _('Display fullscreen snellen chart.')) 683 self.Bind(wx.EVT_MENU, self.__on_snellen, item) 684 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.')) 685 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item) 686 item = self.menu_tools.Append(-1, 'arriba', _('arriba: cardiovascular risk assessment (%s).') % 'www.arriba-hausarzt.de') 687 self.Bind(wx.EVT_MENU, self.__on_arriba, item) 688 if not gmShellAPI.detect_external_binary(binary = 'arriba')[0]: 689 _log.info('<arriba> not found, disabling "arriba" menu item') 690 self.menu_tools.Enable(id = item.Id, enable = False) 691 692 menu_lab = wx.Menu() 693 item = menu_lab.Append(-1, _('Show HL7'), _('Show formatted data from HL7 file')) 694 self.Bind(wx.EVT_MENU, self.__on_show_hl7, item) 695 item = menu_lab.Append(-1, _('Unwrap XML'), _('Unwrap HL7 data from XML file (Excelleris, ...)')) 696 self.Bind(wx.EVT_MENU, self.__on_unwrap_hl7_from_xml, item) 697 item = menu_lab.Append(-1, _('Stage HL7'), _('Stage HL7 data from file')) 698 self.Bind(wx.EVT_MENU, self.__on_stage_hl7, item) 699 item = menu_lab.Append(-1, _('Browse pending'), _('Browse pending (staged) incoming data')) 700 self.Bind(wx.EVT_MENU, self.__on_incoming, item) 701 702 self.menu_tools.Append(wx.NewId(), _('Lab results ...'), menu_lab) 703 704 self.menu_tools.AppendSeparator() 705 706 self.mainmenu.Append(self.menu_tools, _("&Tools")) 707 self.__gb['main.toolsmenu'] = self.menu_tools 708 709 # -- menu "Knowledge" --------------------- 710 menu_knowledge = wx.Menu() 711 712 # -- Knowledge / Drugs 713 menu_drug_dbs = wx.Menu() 714 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.')) 715 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item) 716 # # - IFAP drug DB 717 # item = menu_drug_dbs.Append(-1, u'ifap', _('Start "ifap index PRAXIS" %s drug browser (Windows/Wine, Germany)') % gmTools.u_registered_trademark) 718 # self.Bind(wx.EVT_MENU, self.__on_ifap, item) 719 item = menu_drug_dbs.Append(-1, 'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)')) 720 self.Bind(wx.EVT_MENU, self.__on_kompendium_ch, item) 721 menu_knowledge.Append(wx.NewId(), _('&Drug Resources'), menu_drug_dbs) 722 723 # menu_knowledge.AppendSeparator() 724 725 item = menu_knowledge.Append(-1, _('Medical links (www)'), _('Show a page of links to useful medical content.')) 726 self.Bind(wx.EVT_MENU, self.__on_medical_links, item) 727 728 self.mainmenu.Append(menu_knowledge, _('&Knowledge')) 729 self.__gb['main.knowledgemenu'] = menu_knowledge 730 731 # -- menu "Office" -------------------- 732 self.menu_office = wx.Menu() 733 734 item = self.menu_office.Append(-1, _('&Audit trail'), _('Display database audit trail.')) 735 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item) 736 737 self.menu_office.AppendSeparator() 738 739 item = self.menu_office.Append(-1, _('&Bills'), _('List all bills across all patients.')) 740 self.Bind(wx.EVT_MENU, self.__on_show_all_bills, item) 741 742 item = self.menu_office.Append(-1, _('&Organizations'), _('Manage organizations.')) 743 self.Bind(wx.EVT_MENU, self.__on_manage_orgs, item) 744 745 self.mainmenu.Append(self.menu_office, _('&Office')) 746 self.__gb['main.officemenu'] = self.menu_office 747 748 # -- menu "Help" -------------- 749 help_menu = wx.Menu() 750 help_menu.Append(-1, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.')) 751 self.Bind(wx.EVT_MENU, self.__on_display_wiki, item) 752 help_menu.Append(-1, _('User manual (www)'), _('Go to the User Manual on the web.')) 753 self.Bind(wx.EVT_MENU, self.__on_display_user_manual_online, item) 754 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.')) 755 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item) 756 item = help_menu.Append(-1, _('Browse work dir'), _('Browse user working directory [%s].') % os.path.join(gmTools.gmPaths().home_dir, 'gnumed')) 757 self.Bind(wx.EVT_MENU, self.__on_browse_work_dir, item) 758 759 menu_debugging = wx.Menu() 760 item = menu_debugging.Append(-1, _('Status line: &Clear'), _('Clear the status line.')) 761 self.Bind(wx.EVT_MENU, self.__on_clear_status_line, item) 762 item = menu_debugging.Append(-1, _('Status line: History'), _('Show status line history.')) 763 self.Bind(wx.EVT_MENU, self.__on_show_status_line_history, item) 764 item = menu_debugging.Append(-1, _('Tooltips on'), _('Globally enable tooltips.')) 765 self.Bind(wx.EVT_MENU, self.__on_enable_tooltips, item) 766 item = menu_debugging.Append(-1, _('Tooltips off'), _('Globally (attempt to) disable tooltips.')) 767 self.Bind(wx.EVT_MENU, self.__on_disable_tooltips, item) 768 item = menu_debugging.Append(-1, _('Screenshot'), _('Save a screenshot of this GNUmed client.')) 769 self.Bind(wx.EVT_MENU, self.__on_save_screenshot, item) 770 item = menu_debugging.Append(-1, _('Show log file'), _('Show log file in text viewer.')) 771 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item) 772 item = menu_debugging.Append(-1, _('Backup log file'), _('Backup content of the log to another file.')) 773 self.Bind(wx.EVT_MENU, self.__on_backup_log_file, item) 774 item = menu_debugging.Append(-1, _('Email log file'), _('Send log file to the authors for help.')) 775 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item) 776 item = menu_debugging.Append(-1, _('Browse tmp dir'), _('Browse temporary directory [%s].') % gmTools.gmPaths().tmp_dir) 777 self.Bind(wx.EVT_MENU, self.__on_browse_tmp_dir, item) 778 item = menu_debugging.Append(-1, _('Browse internal work dir'), _('Browse internal working directory [%s].') % os.path.join(gmTools.gmPaths().home_dir, '.gnumed')) 779 self.Bind(wx.EVT_MENU, self.__on_browse_internal_work_dir, item) 780 item = menu_debugging.Append(-1, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.')) 781 self.Bind(wx.EVT_MENU, self.__on_display_bugtracker, item) 782 item = menu_debugging.Append(-1, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.')) 783 self.Bind(wx.EVT_MENU, self.__on_unblock_cursor, item) 784 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.')) 785 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item) 786 # item = menu_debugging.Append(-1, _('Reload hook script'), _('Reload hook script from hard drive.')) 787 # self.Bind(wx.EVT_MENU, self.__on_reload_hook_script, item) 788 if _cfg.get(option = 'debug'): 789 item = menu_debugging.Append(-1, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !')) 790 self.Bind(wx.EVT_MENU, self.__on_toggle_patient_lock, item) 791 item = menu_debugging.Append(-1, _('Test error handling'), _('Throw an exception to test error handling.')) 792 self.Bind(wx.EVT_MENU, self.__on_test_exception, item) 793 item = menu_debugging.Append(-1, _('Test access violation exception'), _('Simulate an access violation exception.')) 794 self.Bind(wx.EVT_MENU, self.__on_test_access_violation, item) 795 item = menu_debugging.Append(-1, _('Test access checking'), _('Simulate a failing access check.')) 796 self.Bind(wx.EVT_MENU, self.__on_test_access_checking, item) 797 item = menu_debugging.Append(-1, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).')) 798 self.Bind(wx.EVT_MENU, self.__on_invoke_inspector, item) 799 try: 800 import wx.lib.inspection 801 except ImportError: 802 menu_debugging.Enable(id = ID, enable = False) 803 try: 804 import faulthandler 805 item = menu_debugging.Append(-1, _('Test fault handler'), _('Simulate a catastrophic fault (SIGSEGV).')) 806 self.Bind(wx.EVT_MENU, self.__on_test_segfault, item) 807 except ImportError: 808 pass 809 item = menu_debugging.Append(-1, _('Test placeholder'), _('Manually test placeholders')) 810 self.Bind(wx.EVT_MENU, self.__on_test_placeholders, item) 811 # help debugging hang with document insertion 812 813 help_menu.Append(wx.NewId(), _('Debugging ...'), menu_debugging) 814 help_menu.AppendSeparator() 815 816 item = help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), '') 817 self.Bind(wx.EVT_MENU, self.OnAbout, item) 818 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.')) 819 self.Bind(wx.EVT_MENU, self.__on_about_database, item) 820 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors')) 821 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item) 822 help_menu.AppendSeparator() 823 824 self.mainmenu.Append(help_menu, _("&Help")) 825 # among other things the Manual is added from a plugin 826 self.__gb['main.helpmenu'] = help_menu 827 828 # and activate menu structure 829 self.SetMenuBar(self.mainmenu)
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 #----------------------------------------------
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):
1049 1050 #----------------------------------------------
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):
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):
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 #---------------------------------------------- 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 #----------------------------------------------
2408 - def __on_menu_reference(self, evt):
2409 gmNetworkTools.open_url_in_browser(url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference')
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 #---------------------------------------------- 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 #----------------------------------------------
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 #----------------------------------------------
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 #----------------------------------------------
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