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