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

Source Code for Module Gnumed.wxpython.gmGuiMain

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