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

Source Code for Module Gnumed.wxpython.gmGuiMain

   1  # -*- coding: utf-8 -*- 
   2   
   3  __doc__ = """GNUmed GUI client. 
   4   
   5  This contains the GUI application framework and main window 
   6  of the all signing all dancing GNUmed Python Reference 
   7  client. It relies on the <gnumed.py> launcher having set up 
   8  the non-GUI-related runtime environment. 
   9   
  10  copyright: authors 
  11  """ 
  12  #============================================================================== 
  13  __author__  = "H. Herb <hherb@gnumed.net>,\ 
  14                             K. Hilbert <Karsten.Hilbert@gmx.net>,\ 
  15                             I. Haywood <i.haywood@ugrad.unimelb.edu.au>" 
  16  __license__ = 'GPL v2 or later (details at http://www.gnu.org)' 
  17   
  18  # stdlib 
  19  import sys 
  20  import time 
  21  import os 
  22  import os.path 
  23  import datetime as pyDT 
  24  import shutil 
  25  import logging 
  26  import urllib.request 
  27  import subprocess 
  28  import glob 
  29  import io 
  30   
  31  _log = logging.getLogger('gm.main') 
  32   
  33   
  34  # GNUmed libs 
  35  from Gnumed.pycommon import gmCfg2 
  36  _cfg = gmCfg2.gmCfgData() 
  37   
  38   
  39  # 3rd party libs: wxPython 
  40  try: 
  41          import wx 
  42          _log.info('wxPython version loaded: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo)) 
  43  except ImportError: 
  44          _log.exception('cannot import wxPython') 
  45          print('GNUmed startup: Cannot import wxPython library.') 
  46          print('GNUmed startup: Make sure wxPython is installed.') 
  47          print('CRITICAL ERROR: Error importing wxPython. Halted.') 
  48          raise 
  49   
  50  # do this check just in case, so we can make sure 
  51  # py2exe and friends include the proper version, too 
  52  version = int('%s%s' % (wx.MAJOR_VERSION, wx.MINOR_VERSION)) 
  53  if (version < 28) or ('unicode' not in wx.PlatformInfo): 
  54          print('GNUmed startup: Unsupported wxPython version (%s: %s).' % (wx.VERSION_STRING, wx.PlatformInfo)) 
  55          print('GNUmed startup: wxPython 2.8+ with unicode support is required.') 
  56          print('CRITICAL ERROR: Proper wxPython version not found. Halted.') 
  57          raise ValueError('wxPython 2.8+ with unicode support not found') 
  58   
  59   
  60  # more GNUmed libs 
  61  from Gnumed.pycommon import gmCfg 
  62  from Gnumed.pycommon import gmPG2 
  63  from Gnumed.pycommon import gmDispatcher 
  64  from Gnumed.pycommon import gmGuiBroker 
  65  from Gnumed.pycommon import gmI18N 
  66  from Gnumed.pycommon import gmExceptions 
  67  from Gnumed.pycommon import gmShellAPI 
  68  from Gnumed.pycommon import gmTools 
  69  from Gnumed.pycommon import gmDateTime 
  70  from Gnumed.pycommon import gmHooks 
  71  from Gnumed.pycommon import gmBackendListener 
  72  from Gnumed.pycommon import gmLog2 
  73  from Gnumed.pycommon import gmNetworkTools 
  74  from Gnumed.pycommon import gmMimeLib 
  75  from Gnumed.pycommon import gmConnectionPool 
  76   
  77  from Gnumed.business import gmPerson 
  78  from Gnumed.business import gmClinicalRecord 
  79  from Gnumed.business import gmPraxis 
  80  from Gnumed.business import gmEMRStructItems 
  81  from Gnumed.business import gmArriba 
  82  from Gnumed.business import gmStaff 
  83   
  84  from Gnumed.exporters import gmPatientExporter 
  85   
  86  from Gnumed.wxpython import gmGuiHelpers 
  87  from Gnumed.wxpython import gmHorstSpace 
  88  from Gnumed.wxpython import gmDemographicsWidgets 
  89  from Gnumed.wxpython import gmPersonCreationWidgets 
  90  from Gnumed.wxpython import gmEMRStructWidgets 
  91  from Gnumed.wxpython import gmPatSearchWidgets 
  92  from Gnumed.wxpython import gmAllergyWidgets 
  93  from Gnumed.wxpython import gmListWidgets 
  94  from Gnumed.wxpython import gmProviderInboxWidgets 
  95  from Gnumed.wxpython import gmCfgWidgets 
  96  from Gnumed.wxpython import gmExceptionHandlingWidgets 
  97  from Gnumed.wxpython import gmNarrativeWorkflows 
  98  from Gnumed.wxpython import gmPhraseWheel 
  99  from Gnumed.wxpython import gmMedicationWidgets 
 100  from Gnumed.wxpython import gmStaffWidgets 
 101  from Gnumed.wxpython import gmDocumentWidgets 
 102  from Gnumed.wxpython import gmTimer 
 103  from Gnumed.wxpython import gmMeasurementWidgets 
 104  from Gnumed.wxpython import gmFormWidgets 
 105  from Gnumed.wxpython import gmSnellen 
 106  from Gnumed.wxpython import gmVaccWidgets 
 107  from Gnumed.wxpython import gmPersonContactWidgets 
 108  from Gnumed.wxpython import gmI18nWidgets 
 109  from Gnumed.wxpython import gmCodingWidgets 
 110  from Gnumed.wxpython import gmOrganizationWidgets 
 111  from Gnumed.wxpython import gmAuthWidgets 
 112  from Gnumed.wxpython import gmFamilyHistoryWidgets 
 113  from Gnumed.wxpython import gmDataPackWidgets 
 114  from Gnumed.wxpython import gmContactWidgets 
 115  from Gnumed.wxpython import gmAddressWidgets 
 116  from Gnumed.wxpython import gmBillingWidgets 
 117  from Gnumed.wxpython import gmKeywordExpansionWidgets 
 118  from Gnumed.wxpython import gmAccessPermissionWidgets 
 119  from Gnumed.wxpython import gmPraxisWidgets 
 120  from Gnumed.wxpython import gmEncounterWidgets 
 121  from Gnumed.wxpython import gmAutoHintWidgets 
 122  from Gnumed.wxpython import gmPregWidgets 
 123  from Gnumed.wxpython import gmExternalCareWidgets 
 124  from Gnumed.wxpython import gmHabitWidgets 
 125  from Gnumed.wxpython import gmSubstanceMgmtWidgets 
 126  from Gnumed.wxpython import gmATCWidgets 
 127  from Gnumed.wxpython import gmLOINCWidgets 
 128  from Gnumed.wxpython import gmVisualProgressNoteWidgets 
 129  from Gnumed.wxpython import gmHospitalStayWidgets 
 130  from Gnumed.wxpython import gmProcedureWidgets 
 131   
 132   
 133  _provider = None 
 134  _scripting_listener = None 
 135  _original_wxEndBusyCursor = None 
136 137 #============================================================================== 138 -class cLog_wx2gm(wx.Log):
139 # redirect wx.LogXXX() calls to python logging log
140 - def DoLogTextAtLevel(self, level, msg):
141 _log.log(level, msg)
142 143 __wxlog = cLog_wx2gm() 144 _log.info('redirecting wx.Log to [%s]', __wxlog) 145 wx.Log.SetActiveTarget(__wxlog)
146 #wx.LogDebug('test message') 147 148 #============================================================================== 149 -class gmTopLevelFrame(wx.Frame):
150 """GNUmed client's main windows frame. 151 152 This is where it all happens. Avoid popping up any other windows. 153 Most user interaction should happen to and from widgets within this frame 154 """ 155 #----------------------------------------------
156 - def __init__(self, parent, id, title, size=wx.DefaultSize):
157 """You'll have to browse the source to understand what the constructor does 158 """ 159 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE) 160 161 self.__setup_font() 162 163 self.__gb = gmGuiBroker.GuiBroker() 164 self.__pre_exit_callbacks = [] 165 self.bar_width = -1 166 self.menu_id2plugin = {} 167 168 _log.info('workplace is >>>%s<<<', gmPraxis.gmCurrentPraxisBranch().active_workplace) 169 170 self.setup_statusbar() 171 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % ( 172 gmTools.coalesce(_provider['title'], ''), 173 _provider['firstnames'][:1], 174 _provider['lastnames'], 175 _provider['short_alias'], 176 _provider['db_user'] 177 )) 178 self.__setup_main_menu() 179 180 self.__set_window_title_template() 181 self.__update_window_title() 182 183 #icon_bundle = wx.IconBundle() 184 #icon_bundle.AddIcon(wx.Icon("my_icon_16_16.ico", wx.BITMAP_TYPE_ICO)) 185 #icon_bundle.AddIcon(wx.Icon("my_icon_32_32.ico", wx.BITMAP_TYPE_ICO)) 186 #self.SetIcons(icon_bundle) 187 self.SetIcon(gmTools.get_icon(wx = wx)) 188 189 self.__register_events() 190 191 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1) 192 self.vbox = wx.BoxSizer(wx.VERTICAL) 193 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1) 194 195 self.SetAutoLayout(True) 196 self.SetSizerAndFit(self.vbox) 197 198 # don't allow the window to get too small 199 # setsizehints only allows minimum size, therefore window can't become small enough 200 # effectively we need the font size to be configurable according to screen size 201 #self.vbox.SetSizeHints(self) 202 self.__set_GUI_size()
203 204 #----------------------------------------------
205 - def __setup_font(self):
206 207 font = self.GetFont() 208 _log.debug('system default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc()) 209 210 desired_font_face = _cfg.get ( 211 group = 'workplace', 212 option = 'client font', 213 source_order = [ 214 ('explicit', 'return'), 215 ('workbase', 'return'), 216 ('local', 'return'), 217 ('user', 'return'), 218 ('system', 'return') 219 ] 220 ) 221 222 fonts2try = [] 223 if desired_font_face is not None: 224 _log.info('client is configured to use font [%s]', desired_font_face) 225 fonts2try.append(desired_font_face) 226 227 if wx.Platform == '__WXMSW__': 228 sane_font_face = 'Noto Sans' 229 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face) 230 fonts2try.append(sane_font_face) 231 sane_font_face = 'DejaVu Sans' 232 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face) 233 fonts2try.append(sane_font_face) 234 235 if len(fonts2try) == 0: 236 return 237 238 for font_face in fonts2try: 239 success = font.SetFaceName(font_face) 240 if success: 241 self.SetFont(font) 242 _log.debug('switched font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc()) 243 return 244 font = self.GetFont() 245 _log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face) 246 247 return
248 249 #----------------------------------------------
250 - def __set_GUI_size(self):
251 """Try to get previous window size from backend.""" 252 253 cfg = gmCfg.cCfgSQL() 254 width = int(cfg.get2 ( 255 option = 'main.window.width', 256 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 257 bias = 'workplace', 258 default = 800 259 )) 260 height = int(cfg.get2 ( 261 option = 'main.window.height', 262 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 263 bias = 'workplace', 264 default = 600 265 )) 266 _log.debug('previous GUI size [%sx%s]', width, height) 267 pos_x = int(cfg.get2 ( 268 option = 'main.window.position.x', 269 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 270 bias = 'workplace', 271 default = 0 272 )) 273 pos_y = int(cfg.get2 ( 274 option = 'main.window.position.y', 275 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 276 bias = 'workplace', 277 default = 0 278 )) 279 _log.debug('previous GUI position [%s:%s]', pos_x, pos_y) 280 281 curr_disp_width = wx.DisplaySize()[0] 282 curr_disp_height = wx.DisplaySize()[1] 283 # max size = display 284 if width > curr_disp_width: 285 _log.debug('adjusting GUI width from %s to display width %s', width, curr_disp_width) 286 width = curr_disp_width 287 if height > curr_disp_height: 288 _log.debug('adjusting GUI height from %s to display height %s', height, curr_disp_height) 289 height = curr_disp_height 290 # min size = 100x100 291 if width < 100: 292 _log.debug('adjusting GUI width to minimum of 100 pixel') 293 width = 100 294 if height < 100: 295 _log.debug('adjusting GUI height to minimum of 100 pixel') 296 height = 100 297 _log.info('setting GUI geom to [%sx%s] @ [%s:%s]', width, height, pos_x, pos_y) 298 299 #self.SetClientSize(wx.Size(width, height)) 300 self.SetSize(wx.Size(width, height)) 301 self.SetPosition(wx.Point(pos_x, pos_y))
302 303 #----------------------------------------------
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 ) 2207 2208 try: os.remove(arriba.pdf_result) 2209 except Exception: _log.exception('cannot remove [%s]', arriba.pdf_result) 2210 2211 if doc is None: 2212 return 2213 2214 doc['comment'] = 'arriba: %s' % _('cardiovascular risk assessment') 2215 doc.save() 2216 2217 try: 2218 open(arriba.xml_result).close() 2219 part = doc.add_part(file = arriba.xml_result) 2220 except Exception: 2221 _log.exception('error accessing [%s]', arriba.xml_result) 2222 gmDispatcher.send(signal = 'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False) 2223 2224 if part is None: 2225 return 2226 2227 part['obj_comment'] = 'XML-Daten' 2228 part['filename'] = 'arriba-result.xml' 2229 part.save()
2230 #----------------------------------------------
2231 - def __on_acs_risk_assessment(self, evt):
2232 2233 dbcfg = gmCfg.cCfgSQL() 2234 cmd = dbcfg.get2 ( 2235 option = 'external.tools.acs_risk_calculator_cmd', 2236 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2237 bias = 'user' 2238 ) 2239 2240 if cmd is None: 2241 gmDispatcher.send(signal = 'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True) 2242 return 2243 2244 cwd = os.path.expanduser(os.path.join('~', '.gnumed')) 2245 try: 2246 subprocess.check_call ( 2247 args = (cmd,), 2248 close_fds = True, 2249 cwd = cwd 2250 ) 2251 except (OSError, ValueError, subprocess.CalledProcessError): 2252 _log.exception('there was a problem executing [%s]', cmd) 2253 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True) 2254 return 2255 2256 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d'))) 2257 for pdf in pdfs: 2258 try: 2259 open(pdf).close() 2260 except Exception: 2261 _log.exception('error accessing [%s]', pdf) 2262 gmDispatcher.send(signal = 'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True) 2263 continue 2264 2265 doc = gmDocumentWidgets.save_file_as_new_document ( 2266 parent = self, 2267 filename = pdf, 2268 document_type = 'risk assessment', 2269 pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit'] 2270 ) 2271 2272 try: 2273 os.remove(pdf) 2274 except Exception: 2275 _log.exception('cannot remove [%s]', pdf) 2276 2277 if doc is None: 2278 continue 2279 doc['comment'] = 'arriba: %s' % _('cardiovascular risk assessment') 2280 doc.save() 2281 2282 return
2283 2284 #----------------------------------------------
2285 - def __on_show_hl7(self, evt):
2286 # from Gnumed.business import gmClinicalCalculator 2287 # calc = gmClinicalCalculator.cClinicalCalculator(patient = gmPerson.gmCurrentPatient()) 2288 # result = calc.eGFR_CKD_EPI 2289 # print(u'%s' % result.format(with_formula = True, with_warnings = True, with_variables = True, with_sub_results = True, with_hints = True)) 2290 # return 2291 gmMeasurementWidgets.show_hl7_file(parent = self)
2292 #----------------------------------------------
2293 - def __on_unwrap_hl7_from_xml(self, evt):
2294 gmMeasurementWidgets.unwrap_HL7_from_XML(parent = self)
2295 #----------------------------------------------
2296 - def __on_stage_hl7(self, evt):
2297 gmMeasurementWidgets.stage_hl7_file(parent = self)
2298 #----------------------------------------------
2299 - def __on_incoming(self, evt):
2300 gmMeasurementWidgets.browse_incoming_unmatched(parent = self)
2301 #----------------------------------------------
2302 - def __on_snellen(self, evt):
2303 dlg = gmSnellen.cSnellenCfgDlg() 2304 if dlg.ShowModal() != wx.ID_OK: 2305 return 2306 2307 frame = gmSnellen.cSnellenChart ( 2308 width = dlg.vals[0], 2309 height = dlg.vals[1], 2310 alpha = dlg.vals[2], 2311 mirr = dlg.vals[3], 2312 parent = None 2313 ) 2314 frame.CentreOnScreen(wx.BOTH) 2315 # self.SetTopWindow(frame) 2316 # frame.Destroy = frame.DestroyWhenApp 2317 frame.Show(True)
2318 #---------------------------------------------- 2319 #---------------------------------------------- 2322 2323 #----------------------------------------------
2324 - def __on_jump_to_drug_db(self, evt):
2325 curr_pat = gmPerson.gmCurrentPatient() 2326 if not curr_pat.connected: 2327 curr_pat = None 2328 gmSubstanceMgmtWidgets.jump_to_drug_database(patient = curr_pat)
2329 2330 #----------------------------------------------
2331 - def __on_kompendium_ch(self, evt):
2332 gmNetworkTools.open_url_in_browser(url = 'http://www.kompendium.ch')
2333 2334 #---------------------------------------------- 2335 # Office 2336 #----------------------------------------------
2337 - def __on_display_audit_trail(self, evt):
2338 gmPraxisWidgets.show_audit_trail(parent = self)
2339 2340 #----------------------------------------------
2341 - def __on_show_all_bills(self, evt):
2342 gmBillingWidgets.manage_bills(parent = self)
2343 2344 #----------------------------------------------
2345 - def __on_manage_orgs(self, evt):
2346 gmOrganizationWidgets.manage_orgs(parent = self)
2347 2348 #---------------------------------------------- 2349 # Help / Debugging 2350 #----------------------------------------------
2351 - def __on_save_screenshot(self, evt):
2352 title = gmTools.undecorate_window_title(self.Title.rstrip()) 2353 png_fname = os.path.join ( 2354 gmTools.gmPaths().home_dir, 2355 'gnumed', 2356 'gm-%s-%s.png' % (title, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')) 2357 ) 2358 self.__save_screenshot_to_file(filename = png_fname)
2359 2360 #----------------------------------------------
2361 - def __on_test_exception(self, evt):
2362 raise ValueError('raised ValueError to test exception handling')
2363 2364 #----------------------------------------------
2365 - def __on_test_segfault(self, evt):
2366 import faulthandler 2367 _log.debug('testing faulthandler via SIGSEGV') 2368 faulthandler._sigsegv()
2369 2370 #----------------------------------------------
2371 - def __on_test_placeholders(self, evt):
2372 from Gnumed.wxpython.gmMacro import test_placeholders 2373 test_placeholders()
2374 2375 #----------------------------------------------
2376 - def __on_test_access_violation(self, evt):
2377 raise gmExceptions.AccessDenied ( 2378 _('[-9999]: <access violation test error>'), 2379 source = 'GNUmed code', 2380 code = -9999, 2381 details = _('This is a deliberate AccessDenied exception thrown to test the handling of access violations by means of a decorator.') 2382 )
2383 #---------------------------------------------- 2384 @gmAccessPermissionWidgets.verify_minimum_required_role('admin', activity = _('testing access check for non-existant <admin> role'))
2385 - def __on_test_access_checking(self, evt):
2386 raise gmExceptions.AccessDenied ( 2387 _('[-9999]: <access violation test error>'), 2388 source = 'GNUmed code', 2389 code = -9999, 2390 details = _('This is a deliberate AccessDenied exception. You should not see this message because the role is checked in a decorator.') 2391 )
2392 #----------------------------------------------
2393 - def __on_invoke_inspector(self, evt):
2394 import wx.lib.inspection 2395 wx.lib.inspection.InspectionTool().Show()
2396 #----------------------------------------------
2397 - def __on_display_bugtracker(self, evt):
2398 gmNetworkTools.open_url_in_browser(url = 'https://bugs.launchpad.net/gnumed/')
2399 #----------------------------------------------
2400 - def __on_display_wiki(self, evt):
2401 gmNetworkTools.open_url_in_browser(url = 'http://wiki.gnumed.de')
2402 #----------------------------------------------
2403 - def __on_display_user_manual_online(self, evt):
2404 gmNetworkTools.open_url_in_browser(url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual')
2405 #----------------------------------------------
2406 - def __on_menu_reference(self, evt):
2407 gmNetworkTools.open_url_in_browser(url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference')
2408 #----------------------------------------------
2409 - def __on_pgadmin3(self, evt):
2410 found, cmd = gmShellAPI.detect_external_binary(binary = 'pgadmin3') 2411 if found: 2412 gmShellAPI.run_command_in_shell(cmd, blocking = False) 2413 return 2414 gmDispatcher.send(signal = 'statustext', msg = _('pgAdmin III not found.'), beep = True)
2415 #----------------------------------------------
2416 - def __on_reload_hook_script(self, evt):
2417 if not gmHooks.import_hook_module(reimport = True): 2418 gmDispatcher.send(signal = 'statustext', msg = _('Error reloading hook script.'))
2419 #----------------------------------------------
2420 - def __on_unblock_cursor(self, evt):
2421 wx.EndBusyCursor()
2422 #----------------------------------------------
2423 - def __on_clear_status_line(self, evt):
2424 gmDispatcher.send(signal = 'statustext', msg = '') 2425 self.StatusBar.set_normal_color()
2426 2427 #----------------------------------------------
2428 - def __on_show_status_line_history(self, evt):
2429 self.StatusBar.show_history()
2430 2431 #----------------------------------------------
2432 - def __on_enable_tooltips(self, evt):
2433 wx.ToolTip.Enable(True)
2434 2435 #----------------------------------------------
2436 - def __on_disable_tooltips(self, evt):
2437 wx.ToolTip.Enable(False)
2438 2439 #----------------------------------------------
2440 - def __on_toggle_patient_lock(self, evt):
2441 curr_pat = gmPerson.gmCurrentPatient() 2442 if curr_pat.locked: 2443 curr_pat.force_unlock() 2444 else: 2445 curr_pat.locked = True
2446 #----------------------------------------------
2447 - def __on_show_log_file(self, evt):
2448 gmLog2.flush() 2449 gmMimeLib.call_viewer_on_file(gmLog2._logfile_name, block = False)
2450 #----------------------------------------------
2451 - def __on_backup_log_file(self, evt):
2452 name = os.path.basename(gmLog2._logfile_name) 2453 name, ext = os.path.splitext(name) 2454 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext) 2455 new_path = os.path.expanduser(os.path.join('~', 'gnumed')) 2456 2457 dlg = wx.FileDialog ( 2458 parent = self, 2459 message = _("Save current log as..."), 2460 defaultDir = new_path, 2461 defaultFile = new_name, 2462 wildcard = "%s (*.log)|*.log" % _("log files"), 2463 style = wx.FD_SAVE 2464 ) 2465 choice = dlg.ShowModal() 2466 new_name = dlg.GetPath() 2467 dlg.DestroyLater() 2468 if choice != wx.ID_OK: 2469 return True 2470 2471 _log.warning('syncing log file for backup to [%s]', new_name) 2472 gmLog2.flush() 2473 shutil.copy2(gmLog2._logfile_name, new_name) 2474 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2475 #----------------------------------------------
2476 - def __on_email_log_file(self, evt):
2477 gmExceptionHandlingWidgets.mail_log(parent = self)
2478 2479 #----------------------------------------------
2480 - def __on_browse_tmp_dir(self, evt):
2481 gmMimeLib.call_viewer_on_file(gmTools.gmPaths().tmp_dir, block = False)
2482 2483 #----------------------------------------------
2484 - def __on_browse_work_dir(self, evt):
2485 gmMimeLib.call_viewer_on_file(os.path.join(gmTools.gmPaths().home_dir, 'gnumed'), block = False)
2486 2487 #----------------------------------------------
2488 - def __on_browse_internal_work_dir(self, evt):
2489 gmMimeLib.call_viewer_on_file(os.path.join(gmTools.gmPaths().home_dir, '.gnumed'), block = False)
2490 2491 #---------------------------------------------- 2492 # GNUmed / 2493 #----------------------------------------------
2494 - def OnClose(self, event):
2495 """This is the wx.EVT_CLOSE handler. 2496 2497 - framework still functional 2498 """ 2499 _log.debug('gmTopLevelFrame.OnClose() start') 2500 self._clean_exit() 2501 self.DestroyLater() 2502 _log.debug('gmTopLevelFrame.OnClose() end') 2503 return True
2504 2505 #----------------------------------------------
2506 - def __dermtool (self, event):
2507 import Gnumed.wxpython.gmDermTool as DT 2508 frame = DT.DermToolDialog(None, -1) 2509 frame.Show(True)
2510 2511 #----------------------------------------------
2512 - def __on_start_new_encounter(self, evt):
2513 pat = gmPerson.gmCurrentPatient() 2514 if not pat.connected: 2515 gmDispatcher.send(signal = 'statustext', msg = _('Cannot start new encounter. No active patient.')) 2516 return False 2517 emr = pat.emr 2518 gmEncounterWidgets.start_new_encounter(emr = emr)
2519 #----------------------------------------------
2520 - def __on_list_encounters(self, evt):
2521 pat = gmPerson.gmCurrentPatient() 2522 if not pat.connected: 2523 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.')) 2524 return False 2525 gmEncounterWidgets.select_encounters()
2526 #----------------------------------------------
2527 - def __on_add_health_issue(self, event):
2528 pat = gmPerson.gmCurrentPatient() 2529 if not pat.connected: 2530 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add health issue. No active patient.')) 2531 return False 2532 gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
2533 #----------------------------------------------
2534 - def __on_add_episode(self, event):
2535 pat = gmPerson.gmCurrentPatient() 2536 if not pat.connected: 2537 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add episode. No active patient.')) 2538 return False 2539 gmEMRStructWidgets.edit_episode(parent = self, episode = None)
2540 #----------------------------------------------
2541 - def __on_add_medication(self, evt):
2542 pat = gmPerson.gmCurrentPatient() 2543 if not pat.connected: 2544 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add medication. No active patient.')) 2545 return False 2546 2547 gmMedicationWidgets.edit_intake_of_substance(parent = self, substance = None) 2548 2549 evt.Skip()
2550 #----------------------------------------------
2551 - def __on_manage_allergies(self, evt):
2552 pat = gmPerson.gmCurrentPatient() 2553 if not pat.connected: 2554 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add allergy. No active patient.')) 2555 return False 2556 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1) 2557 dlg.ShowModal()
2558 #----------------------------------------------
2559 - def __on_manage_performed_procedures(self, evt):
2560 pat = gmPerson.gmCurrentPatient() 2561 if not pat.connected: 2562 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage performed procedures. No active patient.')) 2563 return False 2564 gmProcedureWidgets.manage_performed_procedures(parent = self) 2565 evt.Skip()
2566 #----------------------------------------------
2567 - def __on_manage_hospital_stays(self, evt):
2568 pat = gmPerson.gmCurrentPatient() 2569 if not pat.connected: 2570 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage hospitalizations. No active patient.')) 2571 return False 2572 gmHospitalStayWidgets.manage_hospital_stays(parent = self) 2573 evt.Skip()
2574 #----------------------------------------------
2575 - def __on_manage_external_care(self, evt):
2576 pat = gmPerson.gmCurrentPatient() 2577 if not pat.connected: 2578 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage external care. No active patient.')) 2579 return False 2580 gmExternalCareWidgets.manage_external_care(parent = self) 2581 evt.Skip()
2582 #----------------------------------------------
2583 - def __on_edit_occupation(self, evt):
2584 pat = gmPerson.gmCurrentPatient() 2585 if not pat.connected: 2586 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit occupation. No active patient.')) 2587 return False 2588 gmDemographicsWidgets.edit_occupation() 2589 evt.Skip()
2590 2591 #---------------------------------------------- 2592 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2593 - def __on_manage_vaccination(self, evt):
2594 pat = gmPerson.gmCurrentPatient() 2595 if not pat.connected: 2596 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add vaccinations. No active patient.')) 2597 return False 2598 2599 gmVaccWidgets.manage_vaccinations(parent = self, latest_only = False) 2600 evt.Skip()
2601 2602 #---------------------------------------------- 2603 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2604 - def __on_show_latest_vaccinations(self, evt):
2605 pat = gmPerson.gmCurrentPatient() 2606 if not pat.connected: 2607 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage vaccinations. No active patient.')) 2608 return False 2609 2610 gmVaccWidgets.manage_vaccinations(parent = self, latest_only = True) 2611 evt.Skip()
2612 2613 #---------------------------------------------- 2614 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2615 - def __on_show_all_vaccinations_by_indication(self, evt):
2616 pat = gmPerson.gmCurrentPatient() 2617 if not pat.connected: 2618 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage vaccinations. No active patient.')) 2619 return False 2620 2621 gmVaccWidgets.manage_vaccinations(parent = self, latest_only = False, expand_indications = True) 2622 evt.Skip()
2623 2624 #---------------------------------------------- 2625 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage family history'))
2626 - def __on_manage_fhx(self, evt):
2627 pat = gmPerson.gmCurrentPatient() 2628 if not pat.connected: 2629 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage family history. No active patient.')) 2630 return False 2631 2632 gmFamilyHistoryWidgets.manage_family_history(parent = self) 2633 evt.Skip()
2634 #---------------------------------------------- 2635 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2636 - def __on_manage_measurements(self, evt):
2637 pat = gmPerson.gmCurrentPatient() 2638 if not pat.connected: 2639 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage measurements. No active patient.')) 2640 return False 2641 gmMeasurementWidgets.manage_measurements(parent = self, single_selection = True, emr = pat.emr)
2642 #---------------------------------------------- 2643 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('calculate EDC'))
2644 - def __on_calc_edc(self, evt):
2645 pat = gmPerson.gmCurrentPatient() 2646 gmPregWidgets.calculate_edc(parent = self, patient = pat)
2647 2648 #---------------------------------------------- 2649 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage suppressed hints'))
2650 - def __on_manage_suppressed_hints(self, evt):
2651 pat = gmPerson.gmCurrentPatient() 2652 if not pat.connected: 2653 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage suppressed hints. No active patient.')) 2654 return False 2655 gmAutoHintWidgets.manage_suppressed_hints(parent = self, pk_identity = pat.ID)
2656 2657 #----------------------------------------------
2658 - def __on_manage_substance_abuse(self, evt):
2659 pat = gmPerson.gmCurrentPatient() 2660 if not pat.connected: 2661 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage smoking status. No active patient.')) 2662 return False 2663 gmHabitWidgets.manage_substance_abuse(parent = self, patient = pat)
2664 2665 #----------------------------------------------
2666 - def __on_show_emr_summary(self, event):
2667 pat = gmPerson.gmCurrentPatient() 2668 if not pat.connected: 2669 gmDispatcher.send(signal = 'statustext', msg = _('Cannot show EMR summary. No active patient.')) 2670 return False 2671 2672 emr = pat.emr 2673 dlg = wx.MessageDialog ( 2674 parent = self, 2675 message = emr.format_statistics(), 2676 caption = _('EMR Summary'), 2677 style = wx.OK | wx.STAY_ON_TOP 2678 ) 2679 dlg.ShowModal() 2680 dlg.DestroyLater() 2681 return True
2682 #----------------------------------------------
2683 - def __on_search_emr(self, event):
2684 return gmNarrativeWorkflows.search_narrative_in_emr(parent=self)
2685 #----------------------------------------------
2686 - def __on_search_across_emrs(self, event):
2687 gmNarrativeWorkflows.search_narrative_across_emrs(parent=self)
2688 2689 #----------------------------------------------
2690 - def __export_emr_as_textfile(self, event):
2691 pat = gmPerson.gmCurrentPatient() 2692 if not pat.connected: 2693 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR. No active patient.')) 2694 return False 2695 from Gnumed.exporters import gmPatientExporter 2696 exporter = gmPatientExporter.cEmrExport(patient = pat) 2697 fname = gmTools.get_unique_filename(prefix = 'gm-exp-', suffix = '.txt') 2698 output_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace') 2699 exporter.set_output_file(output_file) 2700 exporter.dump_constraints() 2701 exporter.dump_demographic_record(True) 2702 exporter.dump_clinical_record() 2703 exporter.dump_med_docs() 2704 output_file.close() 2705 pat.export_area.add_file(filename = fname, hint = _('EMR as text document'))
2706 2707 #----------------------------------------------
2708 - def __export_emr_as_timeline_xml(self, event):
2709 pat = gmPerson.gmCurrentPatient() 2710 if not pat.connected: 2711 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR. No active patient.')) 2712 return False 2713 wx.BeginBusyCursor() 2714 from Gnumed.exporters import gmTimelineExporter 2715 try: 2716 fname = gmTimelineExporter.create_timeline_file ( 2717 patient = pat, 2718 include_documents = True, 2719 include_vaccinations = True, 2720 include_encounters = True 2721 ) 2722 finally: 2723 wx.EndBusyCursor() 2724 pat.export_area.add_file(filename = fname, hint = _('EMR as timeline file (XML)'))
2725 2726 #----------------------------------------------
2727 - def __export_emr_as_care_structure(self, event):
2728 pat = gmPerson.gmCurrentPatient() 2729 if not pat.connected: 2730 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR. No active patient.')) 2731 return False 2732 2733 wx.BeginBusyCursor() 2734 try: 2735 fname = gmEMRStructItems.export_emr_structure(patient = pat) 2736 pat.export_area.add_file(filename = fname, hint = _('EMR as care structure file')) 2737 except Exception: 2738 _log.exception('error adding EMR structure file to export area') 2739 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR.')) 2740 wx.EndBusyCursor()
2741 2742 #---------------------------------------------- 2743 # def __on_save_emr_by_last_mod(self, event): 2744 # # sanity checks 2745 # pat = gmPerson.gmCurrentPatient() 2746 # if not pat.connected: 2747 # gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal by last modification time. No active patient.')) 2748 # return False 2749 # 2750 # # get file name 2751 # aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files")) 2752 # aDefDir = os.path.expanduser(os.path.join('~', 'gnumed')) 2753 # fname = '%s-%s_%s.txt' % (_('journal_by_last_mod_time'), pat['lastnames'], pat['firstnames']) 2754 # dlg = wx.FileDialog ( 2755 # parent = self, 2756 # message = _("Save patient's EMR journal as..."), 2757 # defaultDir = aDefDir, 2758 # defaultFile = fname, 2759 # wildcard = aWildcard, 2760 # style = wx.FD_SAVE 2761 # ) 2762 # choice = dlg.ShowModal() 2763 # fname = dlg.GetPath() 2764 # dlg.DestroyLater() 2765 # if choice != wx.ID_OK: 2766 # return True 2767 # 2768 # _log.debug('exporting EMR journal (by last mod) to [%s]' % fname) 2769 # 2770 # exporter = gmPatientExporter.cEMRJournalExporter() 2771 # 2772 # wx.BeginBusyCursor() 2773 # try: 2774 # fname = exporter.save_to_file_by_mod_time(filename = fname, patient = pat) 2775 # except Exception: 2776 # wx.EndBusyCursor() 2777 # _log.exception('error exporting EMR') 2778 # gmGuiHelpers.gm_show_error ( 2779 # _('Error exporting patient EMR as journal by last modification time.'), 2780 # _('EMR journal export') 2781 # ) 2782 # return 2783 # wx.EndBusyCursor() 2784 # 2785 # gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as journal by last modification time into file [%s].') % fname, beep=False) 2786 # 2787 # return True 2788 2789 # #---------------------------------------------- 2790 # def __on_save_emr_as_journal(self, event): 2791 # # sanity checks 2792 # pat = gmPerson.gmCurrentPatient() 2793 # if not pat.connected: 2794 # gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.')) 2795 # return False 2796 # # get file name 2797 # aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files")) 2798 # aDefDir = os.path.expanduser(os.path.join('~', 'gnumed')) 2799 # fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames']) 2800 # dlg = wx.FileDialog ( 2801 # parent = self, 2802 # message = _("Save patient's EMR journal as..."), 2803 # defaultDir = aDefDir, 2804 # defaultFile = fname, 2805 # wildcard = aWildcard, 2806 # style = wx.FD_SAVE 2807 # ) 2808 # choice = dlg.ShowModal() 2809 # fname = dlg.GetPath() 2810 # dlg.DestroyLater() 2811 # if choice != wx.ID_OK: 2812 # return True 2813 # 2814 # _log.debug('exporting EMR journal to [%s]' % fname) 2815 # # instantiate exporter 2816 # exporter = gmPatientExporter.cEMRJournalExporter() 2817 # 2818 # wx.BeginBusyCursor() 2819 # try: 2820 # fname = exporter.save_to_file_by_encounter(filename = fname, patient = pat) 2821 # except Exception: 2822 # wx.EndBusyCursor() 2823 # _log.exception('error exporting EMR') 2824 # gmGuiHelpers.gm_show_error ( 2825 # _('Error exporting patient EMR as chronological journal.'), 2826 # _('EMR journal export') 2827 # ) 2828 # return 2829 # wx.EndBusyCursor() 2830 # 2831 # gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False) 2832 # 2833 # return True 2834 2835 #----------------------------------------------
2836 - def __on_export_emr_by_last_mod(self, event):
2837 # sanity checks 2838 pat = gmPerson.gmCurrentPatient() 2839 if not pat.connected: 2840 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal by last modification time. No active patient.')) 2841 return False 2842 2843 exporter = gmPatientExporter.cEMRJournalExporter() 2844 wx.BeginBusyCursor() 2845 try: 2846 fname = exporter.save_to_file_by_mod_time(patient = pat) 2847 except Exception: 2848 wx.EndBusyCursor() 2849 _log.exception('error exporting EMR') 2850 gmGuiHelpers.gm_show_error ( 2851 _('Error exporting patient EMR as journal by last modification time.'), 2852 _('EMR journal export') 2853 ) 2854 return 2855 wx.EndBusyCursor() 2856 2857 pat.export_area.add_file(filename = fname, hint = _('EMR journal by last modification time')) 2858 2859 return True
2860 2861 #----------------------------------------------
2862 - def __on_export_emr_as_journal(self, event):
2863 # sanity checks 2864 pat = gmPerson.gmCurrentPatient() 2865 if not pat.connected: 2866 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.')) 2867 return False 2868 2869 exporter = gmPatientExporter.cEMRJournalExporter() 2870 wx.BeginBusyCursor() 2871 try: 2872 fname = exporter.save_to_file_by_encounter(patient = pat) 2873 except Exception: 2874 wx.EndBusyCursor() 2875 _log.exception('error exporting EMR') 2876 gmGuiHelpers.gm_show_error ( 2877 _('Error exporting patient EMR as chronological journal.'), 2878 _('EMR journal export') 2879 ) 2880 return 2881 wx.EndBusyCursor() 2882 2883 pat.export_area.add_file(filename = fname, hint = _('EMR journal by encounter')) 2884 2885 return True
2886 2887 #----------------------------------------------
2888 - def __on_export_for_medistar(self, event):
2889 gmNarrativeWorkflows.export_narrative_for_medistar_import ( 2890 parent = self, 2891 soap_cats = 'soapu', 2892 encounter = None # IOW, the current one 2893 )
2894 2895 #----------------------------------------------
2896 - def __on_add_tag2person(self, event):
2897 curr_pat = gmPerson.gmCurrentPatient() 2898 if not curr_pat.connected: 2899 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.')) 2900 return 2901 2902 tag = gmDemographicsWidgets.manage_tag_images(parent = self) 2903 if tag is None: 2904 return 2905 2906 tag = curr_pat.add_tag(tag['pk_tag_image']) 2907 msg = _('Edit the comment on tag [%s]') % tag['l10n_description'] 2908 comment = wx.GetTextFromUser ( 2909 message = msg, 2910 caption = _('Editing tag comment'), 2911 default_value = gmTools.coalesce(tag['comment'], ''), 2912 parent = self 2913 ) 2914 2915 if comment == '': 2916 return 2917 2918 if comment.strip() == tag['comment']: 2919 return 2920 2921 if comment == ' ': 2922 tag['comment'] = None 2923 else: 2924 tag['comment'] = comment.strip() 2925 2926 tag.save()
2927 2928 #----------------------------------------------
2929 - def __on_load_external_patient(self, event):
2930 dbcfg = gmCfg.cCfgSQL() 2931 search_immediately = bool(dbcfg.get2 ( 2932 option = 'patient_search.external_sources.immediately_search_if_single_source', 2933 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2934 bias = 'user', 2935 default = 0 2936 )) 2937 gmPatSearchWidgets.get_person_from_external_sources(parent = self, search_immediately = search_immediately, activate_immediately = True)
2938 2939 #----------------------------------------------
2940 - def __on_export_gdt2clipboard(self, event):
2941 curr_pat = gmPerson.gmCurrentPatient() 2942 if not curr_pat.connected: 2943 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.')) 2944 return False 2945 enc = 'cp850' # FIXME: configurable 2946 gdt_name = curr_pat.export_as_gdt(encoding = enc) 2947 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics as GDT to clipboard.')) 2948 gmGuiHelpers.file2clipboard(filename = gdt_name, announce_result = True)
2949 2950 #----------------------------------------------
2951 - def __on_export_vcard2clipboard(self, event):
2952 curr_pat = gmPerson.gmCurrentPatient() 2953 if not curr_pat.connected: 2954 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as VCARD. No active patient.')) 2955 return False 2956 vcf_name = curr_pat.export_as_vcard() 2957 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics as VCARD to clipboard.')) 2958 gmGuiHelpers.file2clipboard(filename = vcf_name, announce_result = True)
2959 2960 #----------------------------------------------
2961 - def __on_export_linuxmednews_xml2clipboard(self, event):
2962 curr_pat = gmPerson.gmCurrentPatient() 2963 if not curr_pat.connected: 2964 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as XML (LinuxMedNews). No active patient.')) 2965 return False 2966 fname = curr_pat.export_as_xml_linuxmednews() 2967 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to XML file [%s].') % fname) 2968 gmGuiHelpers.file2clipboard(filename = fname, announce_result = True)
2969 2970 #----------------------------------------------
2971 - def __on_export_as_gdt(self, event):
2972 curr_pat = gmPerson.gmCurrentPatient() 2973 if not curr_pat.connected: 2974 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.')) 2975 return False 2976 enc = 'cp850' # FIXME: configurable 2977 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'current-patient.gdt')) 2978 curr_pat.export_as_gdt(filename = fname, encoding = enc) 2979 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2980 2981 #----------------------------------------------
2982 - def __on_export_as_vcard(self, event):
2983 curr_pat = gmPerson.gmCurrentPatient() 2984 if not curr_pat.connected: 2985 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as VCARD. No active patient.')) 2986 return False 2987 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'current-patient.vcf')) 2988 curr_pat.export_as_vcard(filename = fname) 2989 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to VCARD file [%s].') % fname)
2990 2991 #----------------------------------------------
2992 - def __on_import_xml_linuxmednews(self, evt):
2993 gmPatSearchWidgets.load_person_from_xml_linuxmednews_via_clipboard()
2994 2995 #----------------------------------------------
2996 - def __on_import_vcard_from_clipboard(self, evt):
2997 gmPatSearchWidgets.load_person_from_vcard_via_clipboard()
2998 2999 #----------------------------------------------
3000 - def __on_import_vcard_from_file(self, evt):
3001 gmPatSearchWidgets.load_person_from_vcard_file()
3002 3003 #----------------------------------------------
3004 - def __on_search_person(self, evt):
3005 gmDispatcher.send(signal = 'focus_patient_search')
3006 #----------------------------------------------
3007 - def __on_create_new_patient(self, evt):
3008 gmPersonCreationWidgets.create_new_person(parent = self, activate = True)
3009 #----------------------------------------------
3010 - def __on_enlist_patient_as_staff(self, event):
3011 pat = gmPerson.gmCurrentPatient() 3012 if not pat.connected: 3013 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add staff member. No active patient.')) 3014 return False 3015 dlg = gmStaffWidgets.cAddPatientAsStaffDlg(parent=self, id=-1) 3016 dlg.ShowModal()
3017 #----------------------------------------------
3018 - def __on_delete_patient(self, event):
3019 pat = gmPerson.gmCurrentPatient() 3020 if not pat.connected: 3021 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete patient. No patient active.')) 3022 return False 3023 gmDemographicsWidgets.disable_identity(identity = pat) 3024 return True
3025 #----------------------------------------------
3026 - def __on_merge_patients(self, event):
3027 gmPatSearchWidgets.merge_patients(parent=self)
3028 #----------------------------------------------
3029 - def __on_add_new_staff(self, event):
3030 """Create new person and add it as staff.""" 3031 if not gmPersonCreationWidgets.create_new_person(parent = self, activate = True): 3032 return 3033 dlg = gmStaffWidgets.cAddPatientAsStaffDlg(parent=self, id=-1) 3034 dlg.ShowModal()
3035 #----------------------------------------------
3036 - def __on_edit_staff_list(self, event):
3037 dlg = gmStaffWidgets.cEditStaffListDlg(parent=self, id=-1) 3038 dlg.ShowModal()
3039 #----------------------------------------------
3040 - def __on_edit_gmdbowner_password(self, evt):
3041 gmAuthWidgets.change_gmdbowner_password()
3042 #----------------------------------------------
3043 - def __on_update_loinc(self, evt):
3044 gmLOINCWidgets.update_loinc_reference_data()
3045 3046 #----------------------------------------------
3047 - def __on_update_atc(self, evt):
3048 gmATCWidgets.update_atc_reference_data()
3049 3050 #----------------------------------------------
3051 - def __on_install_data_packs(self, evt):
3052 gmDataPackWidgets.manage_data_packs(parent = self)
3053 3054 #----------------------------------------------
3055 - def __on_generate_vaccines(self, evt):
3056 gmVaccWidgets.regenerate_generic_vaccines()
3057 3058 #----------------------------------------------
3059 - def _clean_exit(self):
3060 """Cleanup helper. 3061 3062 - should ALWAYS be called when this program is 3063 to be terminated 3064 - ANY code that should be executed before a 3065 regular shutdown should go in here 3066 - framework still functional 3067 """ 3068 _log.debug('gmTopLevelFrame._clean_exit() start') 3069 3070 # shut down backend notifications listener 3071 listener = gmBackendListener.gmBackendListener() 3072 try: 3073 listener.shutdown() 3074 except Exception: 3075 _log.exception('cannot stop backend notifications listener thread') 3076 3077 # shutdown application scripting listener 3078 if _scripting_listener is not None: 3079 try: 3080 _scripting_listener.shutdown() 3081 except Exception: 3082 _log.exception('cannot stop scripting listener thread') 3083 3084 # shutdown timers 3085 self.StatusBar.clock_update_timer.Stop() 3086 gmTimer.shutdown() 3087 gmPhraseWheel.shutdown() 3088 3089 # run synchronous pre-exit callback 3090 for call_back in self.__pre_exit_callbacks: 3091 try: 3092 call_back() 3093 except Exception: 3094 print('*** pre-exit callback failed ***') 3095 print('%s' % call_back) 3096 _log.exception('callback [%s] failed', call_back) 3097 3098 # signal imminent demise to plugins 3099 gmDispatcher.send('application_closing') 3100 3101 # do not show status line messages anymore 3102 gmDispatcher.disconnect(self._on_set_statustext, 'statustext') 3103 3104 # remember GUI size and position 3105 #curr_width, curr_height = self.GetClientSize() 3106 curr_width, curr_height = self.GetSize() 3107 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height)) 3108 curr_pos_x, curr_pos_y = self.GetScreenPosition() 3109 _log.info('GUI position at shutdown: [%s:%s]' % (curr_pos_x, curr_pos_y)) 3110 if 0 not in [curr_width, curr_height]: 3111 dbcfg = gmCfg.cCfgSQL() 3112 try: 3113 dbcfg.set ( 3114 option = 'main.window.width', 3115 value = curr_width, 3116 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace 3117 ) 3118 dbcfg.set ( 3119 option = 'main.window.height', 3120 value = curr_height, 3121 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace 3122 ) 3123 dbcfg.set ( 3124 option = 'main.window.position.x', 3125 value = curr_pos_x, 3126 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace 3127 ) 3128 dbcfg.set ( 3129 option = 'main.window.position.y', 3130 value = curr_pos_y, 3131 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace 3132 ) 3133 except Exception: 3134 _log.exception('cannot save current client window size and/or position') 3135 3136 if _cfg.get(option = 'debug'): 3137 print('---=== GNUmed shutdown ===---') 3138 try: 3139 print(_('You have to manually close this window to finalize shutting down GNUmed.')) 3140 print(_('This is so that you can inspect the console output at your leisure.')) 3141 except UnicodeEncodeError: 3142 print('You have to manually close this window to finalize shutting down GNUmed.') 3143 print('This is so that you can inspect the console output at your leisure.') 3144 print('---=== GNUmed shutdown ===---') 3145 3146 # shutdown GUI exception handling 3147 gmExceptionHandlingWidgets.uninstall_wx_exception_handler() 3148 3149 # are we clean ? 3150 import threading 3151 _log.debug("%s active threads", threading.activeCount()) 3152 for t in threading.enumerate(): 3153 _log.debug('thread %s', t) 3154 if t.name == 'MainThread': 3155 continue 3156 print('GNUmed: waiting for thread [%s] to finish' % t.name) 3157 3158 _log.debug('gmTopLevelFrame._clean_exit() end')
3159 3160 #---------------------------------------------- 3161 # internal API 3162 #----------------------------------------------
3163 - def __set_window_title_template(self):
3164 if _cfg.get(option = 'slave'): 3165 self.__title_template = '%s%s: %%(pat)s [%%(prov)s@%%(wp)s in %%(site)s of %%(prax)s] (%s:%s)' % ( 3166 gmTools._GM_TITLE_PREFIX, 3167 gmTools.u_chain, 3168 _cfg.get(option = 'slave personality'), 3169 _cfg.get(option = 'xml-rpc port') 3170 ) 3171 else: 3172 self.__title_template = '%s: %%(pat)s [%%(prov)s@%%(wp)s in %%(site)s of %%(prax)s]' % gmTools._GM_TITLE_PREFIX
3173 3174 #----------------------------------------------
3175 - def __update_window_title(self):
3176 """Update title of main window based on template. 3177 3178 This gives nice tooltips on iconified GNUmed instances. 3179 3180 User research indicates that in the title bar people want 3181 the date of birth, not the age, so please stick to this 3182 convention. 3183 """ 3184 args = {} 3185 3186 pat = gmPerson.gmCurrentPatient() 3187 if pat.connected: 3188 args['pat'] = '%s %s %s (%s) #%d' % ( 3189 gmTools.coalesce(pat['title'], '', '%.4s'), 3190 pat['firstnames'], 3191 pat['lastnames'], 3192 pat.get_formatted_dob(format = '%Y %b %d'), 3193 pat['pk_identity'] 3194 ) 3195 else: 3196 args['pat'] = _('no patient') 3197 3198 args['prov'] = '%s%s.%s' % ( 3199 gmTools.coalesce(_provider['title'], '', '%s '), 3200 _provider['firstnames'][:1], 3201 _provider['lastnames'] 3202 ) 3203 3204 praxis = gmPraxis.gmCurrentPraxisBranch() 3205 args['wp'] = praxis.active_workplace 3206 args['site'] = praxis['branch'] 3207 args['prax'] = praxis['praxis'] 3208 3209 self.SetTitle(self.__title_template % args)
3210 3211 #----------------------------------------------
3212 - def __save_screenshot_to_file(self, filename=None):
3213 if filename is None: 3214 filename = gmTools.get_unique_filename ( 3215 prefix = 'gm-screenshot-%s-' % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), 3216 suffix = '.png' 3217 ) 3218 return gmGuiHelpers.save_screenshot_to_file(filename = filename, widget = self, settle_time = 500)
3219 3220 #----------------------------------------------
3221 - def setup_statusbar(self):
3222 self.StatusBar = cStatusBar(self) 3223 self.SetStatusText = self.StatusBar.SetStatusText 3224 self.PushStatusText = self.StatusBar.PushStatusText
3225 3226 #------------------------------------------------
3227 - def Lock(self):
3228 """Lock GNUmed client against unauthorized access""" 3229 # FIXME 3230 # for i in range(1, self.nb.GetPageCount()): 3231 # self.nb.GetPage(i).Enable(False) 3232 return
3233 3234 #----------------------------------------------
3235 - def Unlock(self):
3236 """Unlock the main notebook widgets 3237 As long as we are not logged into the database backend, 3238 all pages but the 'login' page of the main notebook widget 3239 are locked; i.e. not accessible by the user 3240 """ 3241 #unlock notebook pages 3242 # for i in range(1, self.nb.GetPageCount()): 3243 # self.nb.GetPage(i).Enable(True) 3244 # go straight to patient selection 3245 # self.nb.AdvanceSelection() 3246 return
3247 #-----------------------------------------------
3248 - def OnPanelSize(self, event):
3249 wx.LayoutAlgorithm().LayoutWindow(self.LayoutMgr, self.nb)
3250
3251 #============================================================================== 3252 -class cStatusBar(wx.StatusBar):
3253
3254 - def __init__(self, *args, **kwargs):
3255 try: 3256 kwargs['style'] = kwargs['style'] | wx.STB_SIZEGRIP | wx.STB_SHOW_TIPS 3257 except KeyError: 3258 kwargs['style'] = wx.STB_SIZEGRIP | wx.STB_SHOW_TIPS 3259 super().__init__(*args, **kwargs) 3260 3261 self.FieldsCount = 2 3262 self.SetStatusWidths([-1, 225]) 3263 3264 self.__msg_fifo = [] 3265 self.__normal_background_colour = self.GetBackgroundColour() 3266 self.__blink_background_color = 'yellow' 3267 self.__times_to_blink = 0 3268 self.__blink_counter = 0 3269 3270 self.clock_update_timer = wx.PyTimer(self._cb_update_clock) 3271 self.clock_update_timer.Start(milliseconds = 1000) 3272 3273 self.Bind(wx.EVT_LEFT_DCLICK, self._on_show_history)
3274 3275 #----------------------------------------------
3276 - def SetStatusText(self, text, i=0, beep=False):
3277 prev = self.previous_text 3278 msg = self.__update_history(text, i) 3279 super().SetStatusText(msg, i) 3280 if beep: 3281 wx.Bell() 3282 self.__initiate_blinking(text, i, prev)
3283 3284 #----------------------------------------------
3285 - def PushStatusText(self, text, field=0):
3286 prev = self.previous_text 3287 msg = self.__update_history(text, field) 3288 super().PushStatusText(msg, field) 3289 self.__initiate_blinking(text, i, prev)
3290 3291 #----------------------------------------------
3292 - def set_normal_color(self):
3293 return self.SetBackgroundColour(self.__normal_background_colour)
3294 3295 #----------------------------------------------
3296 - def show_history(self):
3297 lines = [] 3298 for entry in self.__msg_fifo: 3299 lines.append('%s (%s)' % (entry['text'], ','.join(entry['timestamps']))) 3300 gmGuiHelpers.gm_show_info ( 3301 title = _('Statusbar history'), 3302 info = _( 3303 '%s - now\n' 3304 '\n' 3305 '%s' 3306 ) % ( 3307 gmDateTime.pydt_now_here().strftime('%H:%M'), 3308 '\n'.join(lines) 3309 ) 3310 )
3311 3312 #---------------------------------------------- 3313 # internal API 3314 #----------------------------------------------
3315 - def _cb_update_clock(self):
3316 """Advances date and local time in the second slot. 3317 3318 Also drives blinking activity. 3319 """ 3320 t = time.localtime(time.time()) 3321 st = time.strftime('%Y %b %d %H:%M:%S', t) 3322 self.SetStatusText(st, 1) 3323 if self.__times_to_blink > 0: 3324 self.__perhaps_blink()
3325 3326 #---------------------------------------------- 3339 3340 #----------------------------------------------
3341 - def __initiate_blinking(self, text, field, previous_text):
3342 if field != 0: 3343 return 3344 text = text.strip() 3345 if text == '': 3346 return 3347 if text == previous_text: 3348 return 3349 self.__blink_counter = 0 3350 self.__times_to_blink = 2
3351 3352 #----------------------------------------------
3353 - def _get_previous_text(self):
3354 if len(self.__msg_fifo) == 0: 3355 return None 3356 return self.__msg_fifo[0]['text']
3357 3358 previous_text = property(_get_previous_text) 3359 3360 #----------------------------------------------
3361 - def __update_history(self, text, field):
3362 if field > 0: 3363 return text 3364 3365 text = text.strip() 3366 if text == '': 3367 return text 3368 3369 now = gmDateTime.pydt_now_here().strftime('%H:%M') 3370 if len(self.__msg_fifo) == 0: 3371 self.__msg_fifo.append({'text': text, 'timestamps': [now]}) 3372 return '%s %s' % (now, text) 3373 3374 last = self.__msg_fifo[0] 3375 if text == last['text']: 3376 last['timestamps'].insert(0, now) 3377 return '%s %s (#%s)' % (now, text, len(last['timestamps'])) 3378 3379 self.__msg_fifo.insert(0, {'text': text, 'timestamps': [now]}) 3380 if len(self.__msg_fifo) > 20: 3381 self.__msg_fifo = self.__msg_fifo[:20] 3382 return '%s %s' % (now, text)
3383 3384 #----------------------------------------------
3385 - def _on_show_history(self, evt):
3386 self.show_history()
3387 3388 #----------------------------------------------
3389 - def __print_msg_fifo(self, context=None):
3390 print('----------------------------------') 3391 print('Statusbar history @ [%s]:' % gmDateTime.pydt_now_here().strftime('%H:%M')) 3392 print('\n'.join(self.__msg_fifo)) 3393 print('----------------------------------')
3394 3395 #----------------------------------------------
3396 - def _on_print_history(self, evt):
3397 evt.Skip() 3398 self.__print_msg_fifo()
3399
3400 #============================================================================== 3401 -class gmApp(wx.App):
3402
3403 - def OnInit(self):
3404 3405 if _cfg.get(option = 'debug'): 3406 self.SetAssertMode(wx.APP_ASSERT_EXCEPTION | wx.APP_ASSERT_LOG) 3407 else: 3408 self.SetAssertMode(wx.APP_ASSERT_SUPPRESS) 3409 3410 self.__starting_up = True 3411 3412 # show tooltips for x msecs 3413 wx.ToolTip.SetAutoPop(4000) 3414 3415 gmExceptionHandlingWidgets.install_wx_exception_handler() 3416 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version')) 3417 3418 self.SetAppName('gnumed') # set this so things like "wx.StandardPaths.GetDataDir()" work as expected 3419 self.SetVendorName('gnumed_community') 3420 try: 3421 self.SetAppDisplayName('GNUmed %s' % _cfg.get(option = 'client_version')) 3422 except AttributeError: 3423 _log.info('SetAppDisplayName() not supported') 3424 try: 3425 self.SetVendorDisplayName('The GNUmed Development Community.') 3426 except AttributeError: 3427 _log.info('SetVendorDisplayName() not supported') 3428 paths = gmTools.gmPaths(app_name = 'gnumed', wx = wx) 3429 paths.init_paths(wx = wx, app_name = 'gnumed') 3430 3431 # log display properties 3432 dw, dh = wx.DisplaySize() 3433 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y))) 3434 _log.debug('display size: %s:%s %s mm', dw, dh, wx.DisplaySizeMM()) 3435 for disp_idx in range(wx.Display.GetCount()): 3436 disp = wx.Display(disp_idx) 3437 disp_mode = disp.CurrentMode 3438 _log.debug('display [%s] "%s": primary=%s, client_area=%s, geom=%s, vid_mode=[%sbpp across %sx%spx @%sHz]', 3439 disp_idx, disp.Name, disp.IsPrimary(), disp.ClientArea, disp.Geometry, 3440 disp_mode.bpp, disp_mode.Width, disp_mode.Height, disp_mode.refresh 3441 ) 3442 3443 if not self.__setup_prefs_file(): 3444 return False 3445 3446 gmExceptionHandlingWidgets.set_sender_email(gmPraxis.gmCurrentPraxisBranch().user_email) 3447 3448 self.__guibroker = gmGuiBroker.GuiBroker() 3449 self.__setup_platform() 3450 3451 if not self.__establish_backend_connection(): 3452 return False 3453 if not self.__verify_db_account(): 3454 return False 3455 if not self.__verify_praxis_branch(): 3456 return False 3457 3458 self.__check_db_lang() 3459 self.__update_workplace_list() 3460 3461 if not _cfg.get(option = 'skip-update-check'): 3462 self.__check_for_updates() 3463 3464 if _cfg.get(option = 'slave'): 3465 if not self.__setup_scripting_listener(): 3466 return False 3467 3468 frame = gmTopLevelFrame(None, id = -1, title = _('GNUmed client'), size = (640, 440)) 3469 frame.CentreOnScreen(wx.BOTH) 3470 self.SetTopWindow(frame) 3471 frame.Show(True) 3472 3473 if _cfg.get(option = 'debug'): 3474 self.RedirectStdio() 3475 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window')) 3476 # print this so people know what this window is for 3477 # and don't get suprised when it pops up later 3478 print('---=== GNUmed startup ===---') 3479 print(_('redirecting STDOUT/STDERR to this log window')) 3480 print('---=== GNUmed startup ===---') 3481 3482 self.__setup_user_activity_timer() 3483 self.__register_events() 3484 3485 wx.CallAfter(self._do_after_init) 3486 3487 return True
3488 #----------------------------------------------
3489 - def OnExit(self):
3490 """Called internally by wxPython after EVT_CLOSE has been handled on last frame. 3491 3492 - after destroying all application windows and controls 3493 - before wx.Windows internal cleanup 3494 """ 3495 _log.debug('gmApp.OnExit() start') 3496 3497 self.__shutdown_user_activity_timer() 3498 3499 if _cfg.get(option = 'debug'): 3500 self.RestoreStdio() 3501 sys.stdin = sys.__stdin__ 3502 sys.stdout = sys.__stdout__ 3503 sys.stderr = sys.__stderr__ 3504 3505 top_wins = wx.GetTopLevelWindows() 3506 if len(top_wins) > 0: 3507 _log.debug('%s top level windows still around in <app>.OnExit()', len(top_wins)) 3508 _log.debug(top_wins) 3509 for win in top_wins: 3510 _log.debug('destroying: %s', win) 3511 win.DestroyLater() 3512 3513 _log.debug('gmApp.OnExit() end') 3514 return 0
3515 3516 #----------------------------------------------
3517 - def _on_query_end_session(self, *args, **kwargs):
3518 wx.Bell() 3519 wx.Bell() 3520 wx.Bell() 3521 _log.warning('unhandled event detected: QUERY_END_SESSION') 3522 _log.info('we should be saving ourselves from here') 3523 gmLog2.flush() 3524 print('unhandled event detected: QUERY_END_SESSION')
3525 #----------------------------------------------
3526 - def _on_end_session(self, *args, **kwargs):
3527 wx.Bell() 3528 wx.Bell() 3529 wx.Bell() 3530 _log.warning('unhandled event detected: END_SESSION') 3531 gmLog2.flush() 3532 print('unhandled event detected: END_SESSION')
3533 #----------------------------------------------
3534 - def _on_app_activated(self, evt):
3535 if evt.GetActive(): 3536 if self.__starting_up: 3537 gmHooks.run_hook_script(hook = 'app_activated_startup') 3538 else: 3539 gmHooks.run_hook_script(hook = 'app_activated') 3540 else: 3541 gmHooks.run_hook_script(hook = 'app_deactivated') 3542 3543 evt.Skip()
3544 #----------------------------------------------
3545 - def _on_user_activity(self, evt):
3546 self.user_activity_detected = True 3547 evt.Skip()
3548 #----------------------------------------------
3549 - def _on_user_activity_timer_expired(self, cookie=None):
3550 3551 if self.user_activity_detected: 3552 self.elapsed_inactivity_slices = 0 3553 self.user_activity_detected = False 3554 self.elapsed_inactivity_slices += 1 3555 else: 3556 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices: 3557 # print("User was inactive for 30 seconds.") 3558 pass 3559 3560 self.user_activity_timer.Start(oneShot = True)
3561 #---------------------------------------------- 3562 # internal helpers 3563 #----------------------------------------------
3564 - def _do_after_init(self):
3565 self.__starting_up = False 3566 #gmClinicalRecord.set_func_ask_user(a_func = gmEncounterWidgets.ask_for_encounter_continuation) 3567 self.__guibroker['horstspace.top_panel']._TCTRL_patient_selector.SetFocus() 3568 gmHooks.run_hook_script(hook = 'startup-after-GUI-init')
3569 3570 #----------------------------------------------
3572 self.user_activity_detected = True 3573 self.elapsed_inactivity_slices = 0 3574 # FIXME: make configurable 3575 self.max_user_inactivity_slices = 15 # 15 * 2000ms == 30 seconds 3576 self.user_activity_timer = gmTimer.cTimer ( 3577 callback = self._on_user_activity_timer_expired, 3578 delay = 2000 # hence a minimum of 2 and max of 3.999... seconds after which inactivity is detected 3579 ) 3580 self.user_activity_timer.Start(oneShot=True)
3581 3582 #----------------------------------------------
3584 try: 3585 self.user_activity_timer.Stop() 3586 del self.user_activity_timer 3587 except Exception: 3588 pass
3589 3590 #----------------------------------------------
3591 - def __register_events(self):
3592 self.Bind(wx.EVT_QUERY_END_SESSION, self._on_query_end_session) 3593 self.Bind(wx.EVT_END_SESSION, self._on_end_session) 3594 3595 # You can bind your app to wx.EVT_ACTIVATE_APP which will fire when your 3596 # app gets/looses focus, or you can wx.EVT_ACTIVATE with any of your 3597 # toplevel windows and call evt.GetActive() in the handler to see whether 3598 # it is gaining or loosing focus. 3599 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated) 3600 3601 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity) 3602 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
3603 3604 #----------------------------------------------
3605 - def __check_for_updates(self):
3606 3607 dbcfg = gmCfg.cCfgSQL() 3608 do_check = bool(dbcfg.get2 ( 3609 option = 'horstspace.update.autocheck_at_startup', 3610 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 3611 bias = 'workplace', 3612 default = True 3613 )) 3614 if not do_check: 3615 return 3616 3617 gmCfgWidgets.check_for_updates(do_async = True)
3618 3619 #----------------------------------------------
3621 """Handle all the database related tasks necessary for startup.""" 3622 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')]) 3623 from Gnumed.wxpython import gmAuthWidgets 3624 connected = gmAuthWidgets.connect_to_database ( 3625 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')], 3626 require_version = not override 3627 ) 3628 if connected: 3629 return True 3630 3631 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection") 3632 return False
3633 3634 #----------------------------------------------
3635 - def __verify_db_account(self):
3636 # check account <-> staff member association 3637 global _provider 3638 try: 3639 _provider = gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 3640 except ValueError: 3641 account = gmPG2.get_current_user() 3642 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account) 3643 msg = _( 3644 'The database account [%s] cannot be used as a\n' 3645 'staff member login for GNUmed. There was an\n' 3646 'error retrieving staff details for it.\n\n' 3647 'Please ask your administrator for help.\n' 3648 ) % account 3649 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions')) 3650 return False 3651 3652 # improve exception handler setup 3653 tmp = '%s%s %s (%s = %s)' % ( 3654 gmTools.coalesce(_provider['title'], ''), 3655 _provider['firstnames'], 3656 _provider['lastnames'], 3657 _provider['short_alias'], 3658 _provider['db_user'] 3659 ) 3660 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp) 3661 3662 return True
3663 3664 #----------------------------------------------
3665 - def __verify_praxis_branch(self):
3666 3667 if not gmPraxisWidgets.set_active_praxis_branch(no_parent = True): 3668 return False 3669 3670 creds = gmConnectionPool.gmConnectionPool().credentials 3671 msg = '\n' 3672 msg += _('Database <%s> on <%s>') % ( 3673 creds.database, 3674 gmTools.coalesce(creds.host, 'localhost') 3675 ) 3676 msg += '\n\n' 3677 3678 praxis = gmPraxis.gmCurrentPraxisBranch() 3679 msg += _('Branch "%s" of praxis "%s"\n') % ( 3680 praxis['branch'], 3681 praxis['praxis'] 3682 ) 3683 msg += '\n\n' 3684 3685 banner = praxis.db_logon_banner 3686 if banner.strip() == '': 3687 return True 3688 msg += banner 3689 msg += '\n\n' 3690 3691 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 3692 None, #self.GetTopWindow(), # freezes 3693 -1, 3694 caption = _('Verifying database'), 3695 question = gmTools.wrap(msg, 60, initial_indent = ' ', subsequent_indent = ' '), 3696 button_defs = [ 3697 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True}, 3698 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False} 3699 ] 3700 ) 3701 log_on = dlg.ShowModal() 3702 dlg.DestroyLater() 3703 if log_on == wx.ID_YES: 3704 return True 3705 _log.info('user decided to not connect to this database') 3706 return False
3707 3708 #----------------------------------------------
3709 - def __update_workplace_list(self):
3710 wps = gmPraxis.gmCurrentPraxisBranch().workplaces 3711 if len(wps) == 0: 3712 return 3713 3714 prefs_file = _cfg.get(option = 'user_preferences_file') 3715 gmCfg2.set_option_in_INI_file ( 3716 filename = prefs_file, 3717 group = 'profile %s' % _cfg.get(option = 'backend_profile'), 3718 option = 'last known workplaces', 3719 value = wps 3720 ) 3721 _cfg.reload_file_source(file = prefs_file)
3722 3723 #----------------------------------------------
3724 - def __setup_prefs_file(self):
3725 """Setup access to a config file for storing preferences.""" 3726 3727 paths = gmTools.gmPaths(app_name = 'gnumed', wx = wx) 3728 3729 candidates = [] 3730 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')]) 3731 if explicit_file is not None: 3732 candidates.append(explicit_file) 3733 # provide a few fallbacks in the event the --conf-file isn't writable 3734 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf')) 3735 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf')) 3736 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf')) 3737 3738 prefs_file = None 3739 for candidate in candidates: 3740 try: 3741 open(candidate, 'a+').close() 3742 prefs_file = candidate 3743 break 3744 except IOError: 3745 continue 3746 3747 if prefs_file is None: 3748 msg = _( 3749 'Cannot find configuration file in any of:\n' 3750 '\n' 3751 ' %s\n' 3752 'You may need to use the comand line option\n' 3753 '\n' 3754 ' --conf-file=<FILE>' 3755 ) % '\n '.join(candidates) 3756 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files')) 3757 return False 3758 3759 _cfg.set_option(option = 'user_preferences_file', value = prefs_file) 3760 _log.info('user preferences file: %s', prefs_file) 3761 3762 return True
3763 3764 #----------------------------------------------
3765 - def __setup_scripting_listener(self):
3766 3767 from socket import error as SocketError 3768 from Gnumed.pycommon import gmScriptingListener 3769 from Gnumed.wxpython import gmMacro 3770 3771 slave_personality = gmTools.coalesce ( 3772 _cfg.get ( 3773 group = 'workplace', 3774 option = 'slave personality', 3775 source_order = [ 3776 ('explicit', 'return'), 3777 ('workbase', 'return'), 3778 ('user', 'return'), 3779 ('system', 'return') 3780 ] 3781 ), 3782 'gnumed-client' 3783 ) 3784 _cfg.set_option(option = 'slave personality', value = slave_personality) 3785 3786 # FIXME: handle port via /var/run/ 3787 port = int ( 3788 gmTools.coalesce ( 3789 _cfg.get ( 3790 group = 'workplace', 3791 option = 'xml-rpc port', 3792 source_order = [ 3793 ('explicit', 'return'), 3794 ('workbase', 'return'), 3795 ('user', 'return'), 3796 ('system', 'return') 3797 ] 3798 ), 3799 9999 3800 ) 3801 ) 3802 _cfg.set_option(option = 'xml-rpc port', value = port) 3803 3804 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality) 3805 global _scripting_listener 3806 try: 3807 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor) 3808 except SocketError as e: 3809 _log.exception('cannot start GNUmed XML-RPC server') 3810 gmGuiHelpers.gm_show_error ( 3811 aMessage = ( 3812 'Cannot start the GNUmed server:\n' 3813 '\n' 3814 ' [%s]' 3815 ) % e, 3816 aTitle = _('GNUmed startup') 3817 ) 3818 return False 3819 3820 return True
3821 3822 #----------------------------------------------
3823 - def __setup_platform(self):
3824 3825 import wx.lib.colourdb 3826 wx.lib.colourdb.updateColourDB() 3827 3828 traits = self.GetTraits() 3829 try: 3830 _log.info('desktop environment: [%s]', traits.GetDesktopEnvironment()) 3831 except Exception: 3832 pass 3833 3834 if wx.Platform == '__WXMSW__': 3835 _log.info('running on MS Windows') 3836 elif wx.Platform == '__WXGTK__': 3837 _log.info('running on GTK (probably Linux)') 3838 elif wx.Platform == '__WXMAC__': 3839 _log.info('running on Mac OS') 3840 wx.SystemOptions.SetOptionInt('mac.textcontrol-use-spell-checker', 1) 3841 else: 3842 _log.info('running on an unknown platform (%s)' % wx.Platform)
3843 3844 #----------------------------------------------
3845 - def __check_db_lang(self):
3846 if gmI18N.system_locale is None or gmI18N.system_locale == '': 3847 _log.warning("system locale is undefined (probably meaning 'C')") 3848 return True 3849 3850 curr_db_lang = gmPG2.get_current_user_language() 3851 _log.debug('current user language in DB: %s', curr_db_lang) 3852 if curr_db_lang is None: 3853 _log.info('no language selected in DB, trying to set') 3854 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]: 3855 if len(lang) == 0: 3856 continue 3857 if gmPG2.set_user_language(language = lang): 3858 _log.debug("Successfully set database language to [%s]." % lang) 3859 return True 3860 3861 _log.error('Cannot set database language to [%s].' % lang) 3862 return True 3863 3864 if curr_db_lang == gmI18N.system_locale_level['full']: 3865 _log.debug('Database locale (%s) up to date.' % curr_db_lang) 3866 return True 3867 3868 if curr_db_lang == gmI18N.system_locale_level['country']: 3869 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (curr_db_lang, gmI18N.system_locale)) 3870 return True 3871 3872 if curr_db_lang == gmI18N.system_locale_level['language']: 3873 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (curr_db_lang, gmI18N.system_locale)) 3874 return True 3875 3876 _log.warning('database locale [%s] does not match system locale [%s]' % (curr_db_lang, gmI18N.system_locale)) 3877 sys_lang2ignore = _cfg.get ( 3878 group = 'backend', 3879 option = 'ignored mismatching system locale', 3880 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')] 3881 ) 3882 if gmI18N.system_locale == sys_lang2ignore: 3883 _log.info('configured to ignore system-to-database locale mismatch') 3884 return True 3885 3886 # no match, not ignoring 3887 msg = _( 3888 "The currently selected database language ('%s') does\n" 3889 "not match the current system language ('%s').\n" 3890 "\n" 3891 "Do you want to set the database language to '%s' ?\n" 3892 ) % (curr_db_lang, gmI18N.system_locale, gmI18N.system_locale) 3893 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 3894 None, 3895 -1, 3896 caption = _('Checking database language settings'), 3897 question = msg, 3898 button_defs = [ 3899 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True}, 3900 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False} 3901 ], 3902 show_checkbox = True, 3903 checkbox_msg = _('Remember to ignore language mismatch'), 3904 checkbox_tooltip = _( 3905 'Checking this will make GNUmed remember your decision\n' 3906 'until the system language is changed.\n' 3907 '\n' 3908 'You can also reactivate this inquiry by removing the\n' 3909 'corresponding "ignore" option from the configuration file\n' 3910 '\n' 3911 ' [%s]' 3912 ) % _cfg.get(option = 'user_preferences_file') 3913 ) 3914 decision = dlg.ShowModal() 3915 remember2ignore_this_mismatch = dlg._CHBOX_dont_ask_again.GetValue() 3916 dlg.DestroyLater() 3917 if decision == wx.ID_NO: 3918 if not remember2ignore_this_mismatch: 3919 return True 3920 _log.info('User did not want to set database locale. Ignoring mismatch next time.') 3921 gmCfg2.set_option_in_INI_file ( 3922 filename = _cfg.get(option = 'user_preferences_file'), 3923 group = 'backend', 3924 option = 'ignored mismatching system locale', 3925 value = gmI18N.system_locale 3926 ) 3927 return True 3928 3929 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]: 3930 if len(lang) == 0: 3931 continue 3932 if gmPG2.set_user_language(language = lang): 3933 _log.debug("Successfully set database language to [%s]." % lang) 3934 return True 3935 3936 _log.error('Cannot set database language to [%s].' % lang) 3937 # no match found but user wanted to set language anyways, so force it 3938 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country']) 3939 gmPG2.force_user_language(language = gmI18N.system_locale_level['country']) 3940 return True
3941
3942 #============================================================================== 3943 -def _signal_debugging_monitor(*args, **kwargs):
3944 try: 3945 kwargs['originated_in_database'] 3946 print('==> got notification from database "%s":' % kwargs['signal']) 3947 except KeyError: 3948 print('==> received signal from client: "%s"' % kwargs['signal']) 3949 3950 del kwargs['signal'] 3951 for key in kwargs: 3952 # careful because of possibly limited console output encoding 3953 try: print(' [%s]: %s' % (key, kwargs[key])) 3954 except Exception: print('cannot print signal information')
3955
3956 #============================================================================== 3957 -def _safe_wxEndBusyCursor():
3958 try: _original_wxEndBusyCursor() 3959 except wx.wxAssertionError: pass
3960
3961 #------------------------------------------------------------------------------ 3962 -def setup_safe_wxEndBusyCursor():
3963 # monkey patch wxPython, needed on Windows ... 3964 if os.name != 'nt': 3965 return 3966 print('GNUmed startup: Monkey patching wx.EndBusyCursor...') 3967 global _original_wxEndBusyCursor 3968 _original_wxEndBusyCursor = wx.EndBusyCursor 3969 wx.EndBusyCursor = _safe_wxEndBusyCursor 3970 _log.debug('monkey patched wx.EndBusyCursor:') 3971 _log.debug('[%s] -> [%s]', _original_wxEndBusyCursor, _safe_wxEndBusyCursor)
3972
3973 #============================================================================== 3974 -def setup_callbacks():
3975 gmPerson.set_yielder(wx.Yield) 3976 gmClinicalRecord.set_delayed_executor(wx.CallAfter)
3977
3978 #============================================================================== 3979 -def main():
3980 3981 # make sure signals end up in the main thread, 3982 # no matter the thread they came from 3983 gmDispatcher.set_main_thread_caller(wx.CallAfter) 3984 3985 if _cfg.get(option = 'debug'): 3986 gmDispatcher.connect(receiver = _signal_debugging_monitor) 3987 _log.debug('gmDispatcher signal monitor activated') 3988 3989 setup_safe_wxEndBusyCursor() 3990 3991 setup_callbacks() 3992 3993 # create an instance of our GNUmed main application 3994 # - do not redirect stdio (yet) 3995 # - allow signals to be delivered 3996 app = gmApp(redirect = False, clearSigInt = False) 3997 app.MainLoop()
3998 3999 #============================================================================== 4000 # Main 4001 #============================================================================== 4002 if __name__ == '__main__': 4003 4004 from GNUmed.pycommon import gmI18N 4005 gmI18N.activate_locale() 4006 gmI18N.install_domain() 4007 4008 _log.info('Starting up as main module.') 4009 main() 4010 4011 #============================================================================== 4012