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

Source Code for Module Gnumed.wxpython.gmGuiMain

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