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 dbcfg = gmCfg.cCfgSQL() 982 check_enc = bool(dbcfg.get2 ( 983 option = 'encounter.show_editor_before_patient_change', 984 workplace = gmSurgery.gmCurrentPractice().active_workplace, 985 bias = 'user', 986 default = True # True: if needed, not always unconditionally 987 )) 988 989 if not check_enc: 990 return True 991 992 pat = gmPerson.gmCurrentPatient() 993 emr = pat.get_emr() 994 enc = emr.active_encounter 995 996 # did we add anything to the EMR ? 997 has_narr = enc.has_narrative() 998 has_docs = enc.has_documents() 999 1000 if (not has_narr) and (not has_docs): 1001 return True 1002 1003 empty_aoe = (gmTools.coalesce(enc['assessment_of_encounter'], '').strip() == u'') 1004 zero_duration = (enc['last_affirmed'] == enc['started']) 1005 1006 # all is well anyway 1007 if (not empty_aoe) and (not zero_duration): 1008 return True 1009 1010 if zero_duration: 1011 enc['last_affirmed'] = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone) 1012 1013 # no narrative, presumably only import of docs and done 1014 if not has_narr: 1015 if empty_aoe: 1016 enc['assessment_of_encounter'] = _('only documents added') 1017 enc['pk_type'] = gmEMRStructItems.get_encounter_type(description = 'chart review')[0]['pk'] 1018 # "last_affirmed" should be latest modified_at of relevant docs but that's a lot more involved 1019 enc.save_payload() 1020 return True 1021 1022 # does have narrative 1023 if empty_aoe: 1024 # - work out suitable default 1025 epis = emr.get_episodes_by_encounter() 1026 if len(epis) > 0: 1027 enc_summary = '' 1028 for epi in epis: 1029 enc_summary += '%s; ' % epi['description'] 1030 enc['assessment_of_encounter'] = enc_summary 1031 1032 gmEMRStructWidgets.edit_encounter(parent = self, encounter = enc) 1033 1034 return True
1035 #---------------------------------------------- 1036 # menu "paperwork" 1037 #----------------------------------------------
1038 - def __on_show_docs(self, evt):
1039 gmDispatcher.send(signal='show_document_viewer')
1040 #----------------------------------------------
1041 - def __on_new_letter(self, evt):
1042 pat = gmPerson.gmCurrentPatient() 1043 if not pat.connected: 1044 gmDispatcher.send(signal = 'statustext', msg = _('Cannot write letter. No active patient.'), beep = True) 1045 return True 1046 #gmFormWidgets.create_new_letter(parent = self) 1047 gmFormWidgets.print_doc_from_template(parent = self, keep_a_copy = True)
1048 #---------------------------------------------- 1049 # help menu 1050 #----------------------------------------------
1051 - def OnAbout(self, event):
1052 from Gnumed.wxpython import gmAbout 1053 gmAbout = gmAbout.AboutFrame ( 1054 self, 1055 -1, 1056 _("About GNUmed"), 1057 size=wx.Size(350, 300), 1058 style = wx.MAXIMIZE_BOX, 1059 version = _cfg.get(option = 'client_version') 1060 ) 1061 gmAbout.Centre(wx.BOTH) 1062 gmTopLevelFrame.otherWin = gmAbout 1063 gmAbout.Show(True) 1064 del gmAbout
1065 #----------------------------------------------
1066 - def __on_about_database(self, evt):
1067 praxis = gmSurgery.gmCurrentPractice() 1068 msg = praxis.db_logon_banner 1069 1070 login = gmPG2.get_default_login() 1071 1072 auth = _( 1073 '\n\n' 1074 ' workplace: %s\n' 1075 ' account: %s\n' 1076 ' database: %s\n' 1077 ' server: %s\n' 1078 ) % ( 1079 praxis.active_workplace, 1080 login.user, 1081 login.database, 1082 gmTools.coalesce(login.host, u'<localhost>') 1083 ) 1084 1085 msg += auth 1086 1087 gmGuiHelpers.gm_show_info(msg, _('About database and server'))
1088 #----------------------------------------------
1089 - def __on_show_contributors(self, event):
1090 from Gnumed.wxpython import gmAbout 1091 contribs = gmAbout.cContributorsDlg ( 1092 parent = self, 1093 id = -1, 1094 title = _('GNUmed contributors'), 1095 size = wx.Size(400,600), 1096 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER 1097 ) 1098 contribs.ShowModal() 1099 del contribs 1100 del gmAbout
1101 #---------------------------------------------- 1102 # GNUmed menu 1103 #----------------------------------------------
1104 - def __on_exit_gnumed(self, event):
1105 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler).""" 1106 _log.debug('gmTopLevelFrame._on_exit_gnumed() start') 1107 self.Close(True) # -> calls wx.EVT_CLOSE handler 1108 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1109 #----------------------------------------------
1110 - def __on_check_for_updates(self, evt):
1112 #----------------------------------------------
1113 - def __on_announce_maintenance(self, evt):
1114 send = gmGuiHelpers.gm_show_question ( 1115 _('This will send a notification about database downtime\n' 1116 'to all GNUmed clients connected to your database.\n' 1117 '\n' 1118 'Do you want to send the notification ?\n' 1119 ), 1120 _('Announcing database maintenance downtime') 1121 ) 1122 if not send: 1123 return 1124 gmPG2.send_maintenance_notification()
1125 #---------------------------------------------- 1126 #----------------------------------------------
1127 - def __on_list_configuration(self, evt):
1128 gmCfgWidgets.list_configuration(parent = self)
1129 #---------------------------------------------- 1130 # submenu GNUmed / options / client 1131 #---------------------------------------------- 1132 # def __on_configure_temp_dir(self, evt): 1133 # 1134 # cfg = gmCfg.cCfgSQL() 1135 # 1136 # tmp_dir = gmTools.coalesce ( 1137 # cfg.get2 ( 1138 # option = "horstspace.tmp_dir", 1139 # workplace = gmSurgery.gmCurrentPractice().active_workplace, 1140 # bias = 'workplace' 1141 # ), 1142 # os.path.expanduser(os.path.join('~', '.gnumed', 'tmp')) 1143 # ) 1144 # 1145 # dlg = wx.DirDialog ( 1146 # parent = self, 1147 # message = _('Choose temporary directory ...'), 1148 # defaultPath = tmp_dir, 1149 # style = wx.DD_DEFAULT_STYLE 1150 # ) 1151 # result = dlg.ShowModal() 1152 # tmp_dir = dlg.GetPath() 1153 # dlg.Destroy() 1154 # 1155 # if result != wx.ID_OK: 1156 # return 1157 # 1158 # cfg.set ( 1159 # workplace = gmSurgery.gmCurrentPractice().active_workplace, 1160 # option = "horstspace.tmp_dir", 1161 # value = tmp_dir 1162 # ) 1163 #----------------------------------------------
1164 - def __on_configure_export_chunk_size(self, evt):
1165 1166 def is_valid(value): 1167 try: 1168 i = int(value) 1169 except: 1170 return False, value 1171 if i < 0: 1172 return False, value 1173 if i > (1024 * 1024 * 1024 * 10): # 10 GB 1174 return False, value 1175 return True, i
1176 1177 gmCfgWidgets.configure_string_option ( 1178 message = _( 1179 'Some network installations cannot cope with loading\n' 1180 'documents of arbitrary size in one piece from the\n' 1181 'database (mainly observed on older Windows versions)\n.' 1182 '\n' 1183 'Under such circumstances documents need to be retrieved\n' 1184 'in chunks and reassembled on the client.\n' 1185 '\n' 1186 'Here you can set the size (in Bytes) above which\n' 1187 'GNUmed will retrieve documents in chunks. Setting this\n' 1188 'value to 0 will disable the chunking protocol.' 1189 ), 1190 option = 'horstspace.blob_export_chunk_size', 1191 bias = 'workplace', 1192 default_value = 1024 * 1024, 1193 validator = is_valid 1194 )
1195 #---------------------------------------------- 1196 # submenu GNUmed / database 1197 #----------------------------------------------
1198 - def __on_configure_db_lang(self, event):
1199 1200 langs = gmPG2.get_translation_languages() 1201 1202 for lang in [ 1203 gmI18N.system_locale_level['language'], 1204 gmI18N.system_locale_level['country'], 1205 gmI18N.system_locale_level['full'] 1206 ]: 1207 if lang not in langs: 1208 langs.append(lang) 1209 1210 selected_lang = gmPG2.get_current_user_language() 1211 try: 1212 selections = [langs.index(selected_lang)] 1213 except ValueError: 1214 selections = None 1215 1216 language = gmListWidgets.get_choices_from_list ( 1217 parent = self, 1218 msg = _( 1219 'Please select your database language from the list below.\n' 1220 '\n' 1221 'Your current setting is [%s].\n' 1222 '\n' 1223 'This setting will not affect the language the user interface\n' 1224 'is displayed in but rather that of the metadata returned\n' 1225 'from the database such as encounter types, document types,\n' 1226 'and EMR formatting.\n' 1227 '\n' 1228 'To switch back to the default English language unselect all\n' 1229 'pre-selected languages from the list below.' 1230 ) % gmTools.coalesce(selected_lang, _('not configured')), 1231 caption = _('Configuring database language'), 1232 choices = langs, 1233 selections = selections, 1234 columns = [_('Language')], 1235 data = langs, 1236 single_selection = True, 1237 can_return_empty = True 1238 ) 1239 1240 if language is None: 1241 return 1242 1243 if language == []: 1244 language = None 1245 1246 try: 1247 _provider.get_staff().database_language = language 1248 return 1249 except ValueError: 1250 pass 1251 1252 force_language = gmGuiHelpers.gm_show_question ( 1253 _('The database currently holds no translations for\n' 1254 'language [%s]. However, you can add translations\n' 1255 'for things like document or encounter types yourself.\n' 1256 '\n' 1257 'Do you want to force the language setting to [%s] ?' 1258 ) % (language, language), 1259 _('Configuring database language') 1260 ) 1261 if not force_language: 1262 return 1263 1264 gmPG2.force_user_language(language = language)
1265 #----------------------------------------------
1266 - def __on_configure_db_welcome(self, event):
1267 dlg = gmGuiHelpers.cGreetingEditorDlg(self, -1) 1268 dlg.ShowModal()
1269 #---------------------------------------------- 1270 # submenu GNUmed - config - external tools 1271 #----------------------------------------------
1272 - def __on_configure_ooo_settle_time(self, event):
1273 1274 def is_valid(value): 1275 try: 1276 value = float(value) 1277 return True, value 1278 except: 1279 return False, value
1280 1281 gmCfgWidgets.configure_string_option ( 1282 message = _( 1283 'When GNUmed cannot find an OpenOffice server it\n' 1284 'will try to start one. OpenOffice, however, needs\n' 1285 'some time to fully start up.\n' 1286 '\n' 1287 'Here you can set the time for GNUmed to wait for OOo.\n' 1288 ), 1289 option = 'external.ooo.startup_settle_time', 1290 bias = 'workplace', 1291 default_value = 2.0, 1292 validator = is_valid 1293 ) 1294 #----------------------------------------------
1295 - def __on_configure_drug_data_source(self, evt):
1296 gmMedicationWidgets.configure_drug_data_source(parent = self)
1297 #----------------------------------------------
1298 - def __on_configure_adr_url(self, evt):
1299 1300 # http://www.akdae.de/Arzneimittelsicherheit/UAW-Meldung/UAW-Meldung-online.html 1301 german_default = u'https://dcgma.org/uaw/meldung.php' 1302 1303 def is_valid(value): 1304 value = value.strip() 1305 if value == u'': 1306 return True, german_default 1307 try: 1308 urllib2.urlopen(value) 1309 return True, value 1310 except: 1311 return True, value
1312 1313 gmCfgWidgets.configure_string_option ( 1314 message = _( 1315 'GNUmed will use this URL to access a website which lets\n' 1316 'you report an adverse drug reaction (ADR).\n' 1317 '\n' 1318 'If you leave this empty it will fall back\n' 1319 'to an URL for reporting ADRs in Germany.' 1320 ), 1321 option = 'external.urls.report_ADR', 1322 bias = 'user', 1323 default_value = german_default, 1324 validator = is_valid 1325 ) 1326 #----------------------------------------------
1327 - def __on_configure_vaccine_adr_url(self, evt):
1328 1329 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' 1330 1331 def is_valid(value): 1332 value = value.strip() 1333 if value == u'': 1334 return True, german_default 1335 try: 1336 urllib2.urlopen(value) 1337 return True, value 1338 except: 1339 return True, value
1340 1341 gmCfgWidgets.configure_string_option ( 1342 message = _( 1343 'GNUmed will use this URL to access a website which lets\n' 1344 'you report an adverse vaccination reaction (vADR).\n' 1345 '\n' 1346 'If you set it to a specific address that URL must be\n' 1347 'accessible now. If you leave it empty it will fall back\n' 1348 'to the URL for reporting other adverse drug reactions.' 1349 ), 1350 option = 'external.urls.report_vaccine_ADR', 1351 bias = 'user', 1352 default_value = german_default, 1353 validator = is_valid 1354 ) 1355 #----------------------------------------------
1356 - def __on_configure_measurements_url(self, evt):
1357 1358 german_default = u'http://www.laborlexikon.de', 1359 1360 def is_valid(value): 1361 value = value.strip() 1362 if value == u'': 1363 return True, german_default 1364 try: 1365 urllib2.urlopen(value) 1366 return True, value 1367 except: 1368 return True, value
1369 1370 gmCfgWidgets.configure_string_option ( 1371 message = _( 1372 'GNUmed will use this URL to access an encyclopedia of\n' 1373 'measurement/lab methods from within the measurments grid.\n' 1374 '\n' 1375 'You can leave this empty but to set it to a specific\n' 1376 'address the URL must be accessible now.' 1377 ), 1378 option = 'external.urls.measurements_encyclopedia', 1379 bias = 'user', 1380 default_value = german_default, 1381 validator = is_valid 1382 ) 1383 #----------------------------------------------
1384 - def __on_configure_vaccination_plans_url(self, evt):
1385 1386 german_default = u'http://www.bundesaerztekammer.de/downloads/ImpfempfehlungenRKI2009.pdf' 1387 1388 def is_valid(value): 1389 value = value.strip() 1390 if value == u'': 1391 return True, german_default 1392 try: 1393 urllib2.urlopen(value) 1394 return True, value 1395 except: 1396 return True, value
1397 1398 gmCfgWidgets.configure_string_option ( 1399 message = _( 1400 'GNUmed will use this URL to access a page showing\n' 1401 'vaccination schedules.\n' 1402 '\n' 1403 'You can leave this empty but to set it to a specific\n' 1404 'address the URL must be accessible now.' 1405 ), 1406 option = 'external.urls.vaccination_plans', 1407 bias = 'user', 1408 default_value = german_default, 1409 validator = is_valid 1410 ) 1411 #----------------------------------------------
1412 - def __on_configure_acs_risk_calculator_cmd(self, event):
1413 1414 def is_valid(value): 1415 found, binary = gmShellAPI.detect_external_binary(value) 1416 if not found: 1417 gmDispatcher.send ( 1418 signal = 'statustext', 1419 msg = _('The command [%s] is not found. This may or may not be a problem.') % value, 1420 beep = True 1421 ) 1422 return False, value 1423 return True, binary
1424 1425 gmCfgWidgets.configure_string_option ( 1426 message = _( 1427 'Enter the shell command with which to start the\n' 1428 'the ACS risk assessment calculator.\n' 1429 '\n' 1430 'GNUmed will try to verify the path which may,\n' 1431 'however, fail if you are using an emulator such\n' 1432 'as Wine. Nevertheless, starting the calculator\n' 1433 'will work as long as the shell command is correct\n' 1434 'despite the failing test.' 1435 ), 1436 option = 'external.tools.acs_risk_calculator_cmd', 1437 bias = 'user', 1438 validator = is_valid 1439 ) 1440 #----------------------------------------------
1441 - def __on_configure_visual_soap_cmd(self, event):
1442 gmNarrativeWidgets.configure_visual_progress_note_editor()
1443 #----------------------------------------------
1444 - def __on_configure_freediams_cmd(self, event):
1445 1446 def is_valid(value): 1447 found, binary = gmShellAPI.detect_external_binary(value) 1448 if not found: 1449 gmDispatcher.send ( 1450 signal = 'statustext', 1451 msg = _('The command [%s] is not found.') % value, 1452 beep = True 1453 ) 1454 return False, value 1455 return True, binary
1456 #------------------------------------------ 1457 gmCfgWidgets.configure_string_option ( 1458 message = _( 1459 'Enter the shell command with which to start\n' 1460 'the FreeDiams drug database frontend.\n' 1461 '\n' 1462 'GNUmed will try to verify that path.' 1463 ), 1464 option = 'external.tools.freediams_cmd', 1465 bias = 'workplace', 1466 default_value = None, 1467 validator = is_valid 1468 ) 1469 #----------------------------------------------
1470 - def __on_configure_ifap_cmd(self, event):
1471 1472 def is_valid(value): 1473 found, binary = gmShellAPI.detect_external_binary(value) 1474 if not found: 1475 gmDispatcher.send ( 1476 signal = 'statustext', 1477 msg = _('The command [%s] is not found. This may or may not be a problem.') % value, 1478 beep = True 1479 ) 1480 return False, value 1481 return True, binary
1482 1483 gmCfgWidgets.configure_string_option ( 1484 message = _( 1485 'Enter the shell command with which to start the\n' 1486 'the IFAP drug database.\n' 1487 '\n' 1488 'GNUmed will try to verify the path which may,\n' 1489 'however, fail if you are using an emulator such\n' 1490 'as Wine. Nevertheless, starting IFAP will work\n' 1491 'as long as the shell command is correct despite\n' 1492 'the failing test.' 1493 ), 1494 option = 'external.ifap-win.shell_command', 1495 bias = 'workplace', 1496 default_value = 'C:\Ifapwin\WIAMDB.EXE', 1497 validator = is_valid 1498 ) 1499 #---------------------------------------------- 1500 # submenu GNUmed / config / ui 1501 #----------------------------------------------
1502 - def __on_configure_startup_plugin(self, evt):
1503 1504 dbcfg = gmCfg.cCfgSQL() 1505 # get list of possible plugins 1506 plugin_list = gmTools.coalesce(dbcfg.get2 ( 1507 option = u'horstspace.notebook.plugin_load_order', 1508 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1509 bias = 'user' 1510 ), []) 1511 1512 # get current setting 1513 initial_plugin = gmTools.coalesce(dbcfg.get2 ( 1514 option = u'horstspace.plugin_to_raise_after_startup', 1515 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1516 bias = 'user' 1517 ), u'gmEMRBrowserPlugin') 1518 try: 1519 selections = [plugin_list.index(initial_plugin)] 1520 except ValueError: 1521 selections = None 1522 1523 # now let user decide 1524 plugin = gmListWidgets.get_choices_from_list ( 1525 parent = self, 1526 msg = _( 1527 'Here you can choose which plugin you want\n' 1528 'GNUmed to display after initial startup.\n' 1529 '\n' 1530 'Note that the plugin must not require any\n' 1531 'patient to be activated.\n' 1532 '\n' 1533 'Select the desired plugin below:' 1534 ), 1535 caption = _('Configuration'), 1536 choices = plugin_list, 1537 selections = selections, 1538 columns = [_('GNUmed Plugin')], 1539 single_selection = True 1540 ) 1541 1542 if plugin is None: 1543 return 1544 1545 dbcfg.set ( 1546 option = u'patient_search.plugin_to_raise_after_startup', 1547 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1548 value = plugin 1549 )
1550 #---------------------------------------------- 1551 # submenu GNUmed / config / ui / patient search 1552 #----------------------------------------------
1553 - def __on_configure_quick_pat_search(self, evt):
1554 gmCfgWidgets.configure_boolean_option ( 1555 parent = self, 1556 question = _( 1557 'If there is only one external patient\n' 1558 'source available do you want GNUmed\n' 1559 'to immediately go ahead and search for\n' 1560 'matching patient records ?\n\n' 1561 'If not GNUmed will let you confirm the source.' 1562 ), 1563 option = 'patient_search.external_sources.immediately_search_if_single_source', 1564 button_tooltips = [ 1565 _('Yes, search for matches immediately.'), 1566 _('No, let me confirm the external patient first.') 1567 ] 1568 )
1569 #----------------------------------------------
1570 - def __on_cfg_default_region(self, evt):
1571 gmPersonContactWidgets.configure_default_region()
1572 #----------------------------------------------
1573 - def __on_cfg_default_country(self, evt):
1574 gmPersonContactWidgets.configure_default_country()
1575 #----------------------------------------------
1576 - def __on_configure_dob_reminder_proximity(self, evt):
1577 1578 def is_valid(value): 1579 return gmPG2.is_pg_interval(candidate=value), value
1580 1581 gmCfgWidgets.configure_string_option ( 1582 message = _( 1583 'When a patient is activated GNUmed checks the\n' 1584 "proximity of the patient's birthday.\n" 1585 '\n' 1586 'If the birthday falls within the range of\n' 1587 ' "today %s <the interval you set here>"\n' 1588 'GNUmed will remind you of the recent or\n' 1589 'imminent anniversary.' 1590 ) % u'\u2213', 1591 option = u'patient_search.dob_warn_interval', 1592 bias = 'user', 1593 default_value = '1 week', 1594 validator = is_valid 1595 ) 1596 #----------------------------------------------
1597 - def __on_allow_multiple_new_episodes(self, evt):
1598 1599 gmCfgWidgets.configure_boolean_option ( 1600 parent = self, 1601 question = _( 1602 'When adding progress notes do you want to\n' 1603 'allow opening several unassociated, new\n' 1604 'episodes for a patient at once ?\n' 1605 '\n' 1606 'This can be particularly helpful when entering\n' 1607 'progress notes on entirely new patients presenting\n' 1608 'with a multitude of problems on their first visit.' 1609 ), 1610 option = u'horstspace.soap_editor.allow_same_episode_multiple_times', 1611 button_tooltips = [ 1612 _('Yes, allow for multiple new episodes concurrently.'), 1613 _('No, only allow editing one new episode at a time.') 1614 ] 1615 )
1616 #----------------------------------------------
1617 - def __on_allow_auto_open_episodes(self, evt):
1618 1619 gmCfgWidgets.configure_boolean_option ( 1620 parent = self, 1621 question = _( 1622 'When activating a patient, do you want GNUmed to\n' 1623 'auto-open editors for all active problems that were\n' 1624 'touched upon during the current and the most recent\n' 1625 'encounter ?' 1626 ), 1627 option = u'horstspace.soap_editor.auto_open_latest_episodes', 1628 button_tooltips = [ 1629 _('Yes, auto-open editors for all problems of the most recent encounter.'), 1630 _('No, only auto-open one editor for a new, unassociated problem.') 1631 ] 1632 )
1633 #----------------------------------------------
1634 - def __on_configure_initial_pat_plugin(self, evt):
1635 1636 dbcfg = gmCfg.cCfgSQL() 1637 # get list of possible plugins 1638 plugin_list = gmTools.coalesce(dbcfg.get2 ( 1639 option = u'horstspace.notebook.plugin_load_order', 1640 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1641 bias = 'user' 1642 ), []) 1643 1644 # get current setting 1645 initial_plugin = gmTools.coalesce(dbcfg.get2 ( 1646 option = u'patient_search.plugin_to_raise_after_search', 1647 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1648 bias = 'user' 1649 ), u'gmEMRBrowserPlugin') 1650 try: 1651 selections = [plugin_list.index(initial_plugin)] 1652 except ValueError: 1653 selections = None 1654 1655 # now let user decide 1656 plugin = gmListWidgets.get_choices_from_list ( 1657 parent = self, 1658 msg = _( 1659 'When a patient is activated GNUmed can\n' 1660 'be told to switch to a specific plugin.\n' 1661 '\n' 1662 'Select the desired plugin below:' 1663 ), 1664 caption = _('Configuration'), 1665 choices = plugin_list, 1666 selections = selections, 1667 columns = [_('GNUmed Plugin')], 1668 single_selection = True 1669 ) 1670 1671 if plugin is None: 1672 return 1673 1674 dbcfg.set ( 1675 option = u'patient_search.plugin_to_raise_after_search', 1676 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1677 value = plugin 1678 )
1679 #---------------------------------------------- 1680 # submenu GNUmed / config / encounter 1681 #----------------------------------------------
1682 - def __on_cfg_medication_list_template(self, evt):
1683 gmMedicationWidgets.configure_medication_list_template(parent = self)
1684 #----------------------------------------------
1685 - def __on_cfg_fallback_primary_provider(self, evt):
1686 gmProviderInboxWidgets.configure_fallback_primary_provider(parent = self)
1687 #----------------------------------------------
1688 - def __on_cfg_enc_default_type(self, evt):
1689 enc_types = gmEMRStructItems.get_encounter_types() 1690 1691 gmCfgWidgets.configure_string_from_list_option ( 1692 parent = self, 1693 message = _('Select the default type for new encounters.\n'), 1694 option = 'encounter.default_type', 1695 bias = 'user', 1696 default_value = u'in surgery', 1697 choices = [ e[0] for e in enc_types ], 1698 columns = [_('Encounter type')], 1699 data = [ e[1] for e in enc_types ] 1700 )
1701 #----------------------------------------------
1702 - def __on_cfg_enc_pat_change(self, event):
1703 gmCfgWidgets.configure_boolean_option ( 1704 parent = self, 1705 question = _( 1706 'Do you want GNUmed to show the encounter\n' 1707 'details editor when changing the active patient ?' 1708 ), 1709 option = 'encounter.show_editor_before_patient_change', 1710 button_tooltips = [ 1711 _('Yes, show the encounter editor if it seems appropriate.'), 1712 _('No, never show the encounter editor even if it would seem useful.') 1713 ] 1714 )
1715 #----------------------------------------------
1716 - def __on_cfg_enc_empty_ttl(self, evt):
1717 1718 def is_valid(value): 1719 return gmPG2.is_pg_interval(candidate=value), value
1720 1721 gmCfgWidgets.configure_string_option ( 1722 message = _( 1723 'When a patient is activated GNUmed checks the\n' 1724 'chart for encounters lacking any entries.\n' 1725 '\n' 1726 'Any such encounters older than what you set\n' 1727 'here will be removed from the medical record.\n' 1728 '\n' 1729 'To effectively disable removal of such encounters\n' 1730 'set this option to an improbable value.\n' 1731 ), 1732 option = 'encounter.ttl_if_empty', 1733 bias = 'user', 1734 default_value = '1 week', 1735 validator = is_valid 1736 ) 1737 #----------------------------------------------
1738 - def __on_cfg_enc_min_ttl(self, evt):
1739 1740 def is_valid(value): 1741 return gmPG2.is_pg_interval(candidate=value), value
1742 1743 gmCfgWidgets.configure_string_option ( 1744 message = _( 1745 'When a patient is activated GNUmed checks the\n' 1746 'age of the most recent encounter.\n' 1747 '\n' 1748 'If that encounter is younger than this age\n' 1749 'the existing encounter will be continued.\n' 1750 '\n' 1751 '(If it is really old a new encounter is\n' 1752 ' started, or else GNUmed will ask you.)\n' 1753 ), 1754 option = 'encounter.minimum_ttl', 1755 bias = 'user', 1756 default_value = '1 hour 30 minutes', 1757 validator = is_valid 1758 ) 1759 #----------------------------------------------
1760 - def __on_cfg_enc_max_ttl(self, evt):
1761 1762 def is_valid(value): 1763 return gmPG2.is_pg_interval(candidate=value), value
1764 1765 gmCfgWidgets.configure_string_option ( 1766 message = _( 1767 'When a patient is activated GNUmed checks the\n' 1768 'age of the most recent encounter.\n' 1769 '\n' 1770 'If that encounter is older than this age\n' 1771 'GNUmed will always start a new encounter.\n' 1772 '\n' 1773 '(If it is very recent the existing encounter\n' 1774 ' is continued, or else GNUmed will ask you.)\n' 1775 ), 1776 option = 'encounter.maximum_ttl', 1777 bias = 'user', 1778 default_value = '6 hours', 1779 validator = is_valid 1780 ) 1781 #----------------------------------------------
1782 - def __on_cfg_epi_ttl(self, evt):
1783 1784 def is_valid(value): 1785 try: 1786 value = int(value) 1787 except: 1788 return False, value 1789 return gmPG2.is_pg_interval(candidate=value), value
1790 1791 gmCfgWidgets.configure_string_option ( 1792 message = _( 1793 'At any time there can only be one open (ongoing)\n' 1794 'episode for each health issue.\n' 1795 '\n' 1796 'When you try to open (add data to) an episode on a health\n' 1797 'issue GNUmed will check for an existing open episode on\n' 1798 'that issue. If there is any it will check the age of that\n' 1799 'episode. The episode is closed if it has been dormant (no\n' 1800 'data added, that is) for the period of time (in days) you\n' 1801 'set here.\n' 1802 '\n' 1803 "If the existing episode hasn't been dormant long enough\n" 1804 'GNUmed will consult you what to do.\n' 1805 '\n' 1806 'Enter maximum episode dormancy in DAYS:' 1807 ), 1808 option = 'episode.ttl', 1809 bias = 'user', 1810 default_value = 60, 1811 validator = is_valid 1812 ) 1813 #----------------------------------------------
1814 - def __on_configure_user_email(self, evt):
1815 email = gmSurgery.gmCurrentPractice().user_email 1816 1817 dlg = wx.TextEntryDialog ( 1818 parent = self, 1819 message = _( 1820 'This email address will be used when GNUmed\n' 1821 'is sending email on your behalf such as when\n' 1822 'reporting bugs or when you choose to contribute\n' 1823 'reference material to the GNUmed community.\n' 1824 '\n' 1825 'The developers will then be able to get back to you\n' 1826 'directly with advice. Otherwise you would have to\n' 1827 'follow the mailing list discussion for help.\n' 1828 '\n' 1829 'Leave this blank if you wish to stay anonymous.' 1830 ), 1831 caption = _('Please enter your email address.'), 1832 defaultValue = gmTools.coalesce(email, u''), 1833 style = wx.OK | wx.CANCEL | wx.CENTRE 1834 ) 1835 decision = dlg.ShowModal() 1836 if decision == wx.ID_CANCEL: 1837 dlg.Destroy() 1838 return 1839 1840 email = dlg.GetValue().strip() 1841 gmSurgery.gmCurrentPractice().user_email = email 1842 gmExceptionHandlingWidgets.set_sender_email(email) 1843 dlg.Destroy()
1844 #----------------------------------------------
1845 - def __on_configure_update_check(self, evt):
1846 gmCfgWidgets.configure_boolean_option ( 1847 question = _( 1848 'Do you want GNUmed to check for updates at startup ?\n' 1849 '\n' 1850 'You will still need your system administrator to\n' 1851 'actually install any updates for you.\n' 1852 ), 1853 option = u'horstspace.update.autocheck_at_startup', 1854 button_tooltips = [ 1855 _('Yes, check for updates at startup.'), 1856 _('No, do not check for updates at startup.') 1857 ] 1858 )
1859 #----------------------------------------------
1860 - def __on_configure_update_check_scope(self, evt):
1861 gmCfgWidgets.configure_boolean_option ( 1862 question = _( 1863 'When checking for updates do you want GNUmed to\n' 1864 'look for bug fix updates only or do you want to\n' 1865 'know about features updates, too ?\n' 1866 '\n' 1867 'Minor updates (x.y.z.a -> x.y.z.b) contain bug fixes\n' 1868 'only. They can usually be installed without much\n' 1869 'preparation. They never require a database upgrade.\n' 1870 '\n' 1871 'Major updates (x.y.a -> x..y.b or y.a -> x.b) come\n' 1872 'with new features. They need more preparation and\n' 1873 'often require a database upgrade.\n' 1874 '\n' 1875 'You will still need your system administrator to\n' 1876 'actually install any updates for you.\n' 1877 ), 1878 option = u'horstspace.update.consider_latest_branch', 1879 button_tooltips = [ 1880 _('Yes, check for feature updates, too.'), 1881 _('No, check for bug-fix updates only.') 1882 ] 1883 )
1884 #----------------------------------------------
1885 - def __on_configure_update_url(self, evt):
1886 1887 import urllib2 as url 1888 1889 def is_valid(value): 1890 try: 1891 url.urlopen(value) 1892 except: 1893 return False, value 1894 1895 return True, value
1896 1897 gmCfgWidgets.configure_string_option ( 1898 message = _( 1899 'GNUmed can check for new releases being available. To do\n' 1900 'so it needs to load version information from an URL.\n' 1901 '\n' 1902 'The default URL is:\n' 1903 '\n' 1904 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n' 1905 '\n' 1906 'but you can configure any other URL locally. Note\n' 1907 'that you must enter the location as a valid URL.\n' 1908 'Depending on the URL the client will need online\n' 1909 'access when checking for updates.' 1910 ), 1911 option = u'horstspace.update.url', 1912 bias = u'workplace', 1913 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt', 1914 validator = is_valid 1915 ) 1916 #----------------------------------------------
1917 - def __on_configure_partless_docs(self, evt):
1918 gmCfgWidgets.configure_boolean_option ( 1919 question = _( 1920 'Do you want to allow saving of new documents without\n' 1921 'any parts or do you want GNUmed to enforce that they\n' 1922 'contain at least one part before they can be saved ?\n' 1923 '\n' 1924 'Part-less documents can be useful if you want to build\n' 1925 'up an index of, say, archived documents but do not\n' 1926 'want to scan in all the pages contained therein.' 1927 ), 1928 option = u'horstspace.scan_index.allow_partless_documents', 1929 button_tooltips = [ 1930 _('Yes, allow saving documents without any parts.'), 1931 _('No, require documents to have at least one part.') 1932 ] 1933 )
1934 #----------------------------------------------
1935 - def __on_configure_doc_uuid_dialog(self, evt):
1936 gmCfgWidgets.configure_boolean_option ( 1937 question = _( 1938 'After importing a new document do you\n' 1939 'want GNUmed to display the unique ID\n' 1940 'it auto-generated for that document ?\n' 1941 '\n' 1942 'This can be useful if you want to label the\n' 1943 'originals with that ID for later identification.' 1944 ), 1945 option = u'horstspace.scan_index.show_doc_id', 1946 button_tooltips = [ 1947 _('Yes, display the ID generated for the new document after importing.'), 1948 _('No, do not display the ID generated for the new document after importing.') 1949 ] 1950 )
1951 #----------------------------------------------
1952 - def __on_configure_doc_review_dialog(self, evt):
1953 1954 def is_valid(value): 1955 try: 1956 value = int(value) 1957 except: 1958 return False, value 1959 if value not in [0, 1, 2]: 1960 return False, value 1961 return True, value
1962 1963 gmCfgWidgets.configure_string_option ( 1964 message = _( 1965 'GNUmed can show the document review dialog after\n' 1966 'calling the appropriate viewer for that document.\n' 1967 '\n' 1968 'Select the conditions under which you want\n' 1969 'GNUmed to do so:\n' 1970 '\n' 1971 ' 0: never display the review dialog\n' 1972 ' 1: always display the dialog\n' 1973 ' 2: only if there is no previous review by me\n' 1974 '\n' 1975 'Note that if a viewer is configured to not block\n' 1976 'GNUmed during document display the review dialog\n' 1977 'will actually appear in parallel to the viewer.' 1978 ), 1979 option = u'horstspace.document_viewer.review_after_display', 1980 bias = u'user', 1981 default_value = 2, 1982 validator = is_valid 1983 ) 1984 #----------------------------------------------
1985 - def __on_manage_master_data(self, evt):
1986 1987 # this is how it is sorted 1988 master_data_lists = [ 1989 'adr', 1990 'drugs', 1991 'codes', 1992 'substances_in_brands', 1993 'substances', 1994 'labs', 1995 'form_templates', 1996 'doc_types', 1997 'enc_types', 1998 'text_expansions', 1999 'meta_test_types', 2000 # 'orgs', 2001 'patient_tags', 2002 'provinces', 2003 'db_translations', 2004 'test_types', 2005 'org_units', 2006 'vacc_indications', 2007 'vaccines', 2008 'workplaces' 2009 ] 2010 2011 master_data_list_names = { 2012 'adr': _('Addresses (likely slow)'), 2013 'drugs': _('Branded drugs (as marketed)'), 2014 'codes': _('Codes and their respective terms'), 2015 'substances_in_brands': _('Components of branded drugs (substances in brands)'), 2016 'labs': _('Diagnostic organizations (path labs, ...)'), 2017 'form_templates': _('Document templates (forms, letters, plots, ...)'), 2018 'doc_types': _('Document types'), 2019 'enc_types': _('Encounter types'), 2020 'text_expansions': _('Keyword based text expansion macros'), 2021 'meta_test_types': _('Meta test/measurement types'), 2022 # 'orgs': _('Organizations'), 2023 'patient_tags': _('Patient tags'), 2024 'provinces': _('Provinces (counties, territories, states, regions, ...)'), 2025 'db_translations': _('String translations in the database'), 2026 'test_types': _('Test/measurement types'), 2027 'org_units': _('Units of organizations (branches, sites, departments, parts, ...'), 2028 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'), 2029 'vaccines': _('Vaccines'), 2030 'workplaces': _('Workplace profiles (which plugins to load)'), 2031 'substances': _('Consumable substances') 2032 } 2033 2034 map_list2handler = { 2035 'org_units': gmOrganizationWidgets.manage_org_units, 2036 'form_templates': gmFormWidgets.manage_form_templates, 2037 'doc_types': gmDocumentWidgets.manage_document_types, 2038 'text_expansions': gmProviderInboxWidgets.configure_keyword_text_expansion, 2039 'db_translations': gmI18nWidgets.manage_translations, 2040 'codes': gmCodingWidgets.browse_coded_terms, 2041 'enc_types': gmEMRStructWidgets.manage_encounter_types, 2042 'provinces': gmPersonContactWidgets.manage_provinces, 2043 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins, 2044 'drugs': gmMedicationWidgets.manage_branded_drugs, 2045 'substances_in_brands': gmMedicationWidgets.manage_drug_components, 2046 'labs': gmMeasurementWidgets.manage_measurement_orgs, 2047 'test_types': gmMeasurementWidgets.manage_measurement_types, 2048 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types, 2049 'vaccines': gmVaccWidgets.manage_vaccines, 2050 'vacc_indications': gmVaccWidgets.manage_vaccination_indications, 2051 # 'orgs': gmOrganizationWidgets.manage_orgs, 2052 'adr': gmPersonContactWidgets.manage_addresses, 2053 'substances': gmMedicationWidgets.manage_consumable_substances, 2054 'patient_tags': gmDemographicsWidgets.manage_tag_images 2055 } 2056 2057 #--------------------------------- 2058 def edit(item): 2059 try: map_list2handler[item](parent = self) 2060 except KeyError: pass 2061 return False
2062 #--------------------------------- 2063 2064 gmListWidgets.get_choices_from_list ( 2065 parent = self, 2066 caption = _('Master data management'), 2067 choices = [ master_data_list_names[lst] for lst in master_data_lists], 2068 data = master_data_lists, 2069 columns = [_('Select the list you want to manage:')], 2070 edit_callback = edit, 2071 single_selection = True, 2072 ignore_OK_button = True 2073 ) 2074 #----------------------------------------------
2075 - def __on_dicom_viewer(self, evt):
2076 2077 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK): 2078 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking=False) 2079 return 2080 2081 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']: 2082 found, cmd = gmShellAPI.detect_external_binary(binary = viewer) 2083 if found: 2084 gmShellAPI.run_command_in_shell(cmd, blocking=False) 2085 return 2086 2087 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2088 #----------------------------------------------
2089 - def __on_arriba(self, evt):
2090 curr_pat = gmPerson.gmCurrentPatient() 2091 2092 arriba = gmArriba.cArriba() 2093 if not arriba.run(patient = curr_pat, debug = _cfg.get(option = 'debug')): 2094 return 2095 2096 # FIXME: try to find patient 2097 if curr_pat is None: 2098 return 2099 2100 if arriba.pdf_result is None: 2101 return 2102 2103 doc = gmDocumentWidgets.save_file_as_new_document ( 2104 parent = self, 2105 filename = arriba.pdf_result, 2106 document_type = _('risk assessment') 2107 ) 2108 2109 try: os.remove(arriba.pdf_result) 2110 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result) 2111 2112 if doc is None: 2113 return 2114 2115 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment') 2116 doc.save() 2117 2118 try: 2119 open(arriba.xml_result).close() 2120 part = doc.add_part(file = arriba.xml_result) 2121 except StandardError: 2122 _log.exception('error accessing [%s]', arriba.xml_result) 2123 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False) 2124 2125 if part is None: 2126 return 2127 2128 part['obj_comment'] = u'XML-Daten' 2129 part['filename'] = u'arriba-result.xml' 2130 part.save()
2131 #----------------------------------------------
2132 - def __on_acs_risk_assessment(self, evt):
2133 2134 dbcfg = gmCfg.cCfgSQL() 2135 cmd = dbcfg.get2 ( 2136 option = u'external.tools.acs_risk_calculator_cmd', 2137 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2138 bias = 'user' 2139 ) 2140 2141 if cmd is None: 2142 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True) 2143 return 2144 2145 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp')) 2146 try: 2147 subprocess.check_call ( 2148 args = (cmd,), 2149 close_fds = True, 2150 cwd = cwd 2151 ) 2152 except (OSError, ValueError, subprocess.CalledProcessError): 2153 _log.exception('there was a problem executing [%s]', cmd) 2154 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True) 2155 return 2156 2157 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d'))) 2158 for pdf in pdfs: 2159 try: 2160 open(pdf).close() 2161 except: 2162 _log.exception('error accessing [%s]', pdf) 2163 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True) 2164 continue 2165 2166 doc = gmDocumentWidgets.save_file_as_new_document ( 2167 parent = self, 2168 filename = pdf, 2169 document_type = u'risk assessment' 2170 ) 2171 2172 try: 2173 os.remove(pdf) 2174 except StandardError: 2175 _log.exception('cannot remove [%s]', pdf) 2176 2177 if doc is None: 2178 continue 2179 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment') 2180 doc.save() 2181 2182 return
2183 #----------------------------------------------
2184 - def __on_snellen(self, evt):
2185 dlg = gmSnellen.cSnellenCfgDlg() 2186 if dlg.ShowModal() != wx.ID_OK: 2187 return 2188 2189 frame = gmSnellen.cSnellenChart ( 2190 width = dlg.vals[0], 2191 height = dlg.vals[1], 2192 alpha = dlg.vals[2], 2193 mirr = dlg.vals[3], 2194 parent = None 2195 ) 2196 frame.CentreOnScreen(wx.BOTH) 2197 # self.SetTopWindow(frame) 2198 # frame.Destroy = frame.DestroyWhenApp 2199 frame.Show(True)
2200 #---------------------------------------------- 2201 #---------------------------------------------- 2208 #----------------------------------------------
2209 - def __on_jump_to_drug_db(self, evt):
2210 gmMedicationWidgets.jump_to_drug_database()
2211 #----------------------------------------------
2212 - def __on_kompendium_ch(self, evt):
2213 webbrowser.open ( 2214 url = 'http://www.kompendium.ch', 2215 new = False, 2216 autoraise = True 2217 )
2218 #---------------------------------------------- 2219 # Office 2220 #----------------------------------------------
2221 - def __on_display_audit_trail(self, evt):
2222 gmProviderInboxWidgets.show_audit_trail(parent = self) 2223 evt.Skip()
2224 #---------------------------------------------- 2225 # Help / Debugging 2226 #----------------------------------------------
2227 - def __on_save_screenshot(self, evt):
2228 wx.CallAfter(self.__save_screenshot) 2229 evt.Skip()
2230 #----------------------------------------------
2231 - def __save_screenshot(self):
2232 2233 time.sleep(0.5) 2234 2235 rect = self.GetRect() 2236 2237 # adjust for window decoration on Linux 2238 if sys.platform == 'linux2': 2239 client_x, client_y = self.ClientToScreen((0, 0)) 2240 border_width = client_x - rect.x 2241 title_bar_height = client_y - rect.y 2242 # If the window has a menu bar, remove it from the title bar height. 2243 if self.GetMenuBar(): 2244 title_bar_height /= 2 2245 rect.width += (border_width * 2) 2246 rect.height += title_bar_height + border_width 2247 2248 wdc = wx.ScreenDC() 2249 mdc = wx.MemoryDC() 2250 img = wx.EmptyBitmap(rect.width, rect.height) 2251 mdc.SelectObject(img) 2252 mdc.Blit ( # copy ... 2253 0, 0, # ... to here in the target ... 2254 rect.width, rect.height, # ... that much from ... 2255 wdc, # ... the source ... 2256 rect.x, rect.y # ... starting here 2257 ) 2258 2259 # FIXME: improve filename with patient/workplace/provider, allow user to select/change 2260 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') 2261 img.SaveFile(fname, wx.BITMAP_TYPE_PNG) 2262 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2263 #----------------------------------------------
2264 - def __on_test_exception(self, evt):
2265 #import nonexistant_module 2266 raise ValueError('raised ValueError to test exception handling')
2267 #----------------------------------------------
2268 - def __on_invoke_inspector(self, evt):
2269 import wx.lib.inspection 2270 wx.lib.inspection.InspectionTool().Show()
2271 #----------------------------------------------
2272 - def __on_display_bugtracker(self, evt):
2273 webbrowser.open ( 2274 url = 'https://bugs.launchpad.net/gnumed/', 2275 new = False, 2276 autoraise = True 2277 )
2278 #----------------------------------------------
2279 - def __on_display_wiki(self, evt):
2280 webbrowser.open ( 2281 url = 'http://wiki.gnumed.de', 2282 new = False, 2283 autoraise = True 2284 )
2285 #----------------------------------------------
2286 - def __on_display_user_manual_online(self, evt):
2287 webbrowser.open ( 2288 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual', 2289 new = False, 2290 autoraise = True 2291 )
2292 #----------------------------------------------
2293 - def __on_menu_reference(self, evt):
2294 webbrowser.open ( 2295 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference', 2296 new = False, 2297 autoraise = True 2298 )
2299 #----------------------------------------------
2300 - def __on_pgadmin3(self, evt):
2301 found, cmd = gmShellAPI.detect_external_binary(binary = 'pgadmin3') 2302 if found: 2303 gmShellAPI.run_command_in_shell(cmd, blocking=False) 2304 return 2305 gmDispatcher.send(signal = 'statustext', msg = _('pgAdmin III not found.'), beep = True)
2306 #----------------------------------------------
2307 - def __on_reload_hook_script(self, evt):
2308 if not gmHooks.import_hook_module(reimport = True): 2309 gmDispatcher.send(signal = 'statustext', msg = _('Error reloading hook script.'))
2310 #----------------------------------------------
2311 - def __on_unblock_cursor(self, evt):
2312 wx.EndBusyCursor()
2313 #----------------------------------------------
2314 - def __on_toggle_patient_lock(self, evt):
2315 curr_pat = gmPerson.gmCurrentPatient() 2316 if curr_pat.locked: 2317 curr_pat.force_unlock() 2318 else: 2319 curr_pat.locked = True
2320 #----------------------------------------------
2321 - def __on_show_log_file(self, evt):
2322 from Gnumed.pycommon import gmMimeLib 2323 gmLog2.flush() 2324 gmMimeLib.call_viewer_on_file(gmLog2._logfile_name, block = False)
2325 #----------------------------------------------
2326 - def __on_backup_log_file(self, evt):
2327 name = os.path.basename(gmLog2._logfile_name) 2328 name, ext = os.path.splitext(name) 2329 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext) 2330 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs')) 2331 2332 dlg = wx.FileDialog ( 2333 parent = self, 2334 message = _("Save current log as..."), 2335 defaultDir = new_path, 2336 defaultFile = new_name, 2337 wildcard = "%s (*.log)|*.log" % _("log files"), 2338 style = wx.SAVE 2339 ) 2340 choice = dlg.ShowModal() 2341 new_name = dlg.GetPath() 2342 dlg.Destroy() 2343 if choice != wx.ID_OK: 2344 return True 2345 2346 _log.warning('syncing log file for backup to [%s]', new_name) 2347 gmLog2.flush() 2348 shutil.copy2(gmLog2._logfile_name, new_name) 2349 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2350 #----------------------------------------------
2351 - def __on_email_log_file(self, evt):
2352 gmExceptionHandlingWidgets.mail_log(parent = self)
2353 #---------------------------------------------- 2354 # GNUmed / 2355 #----------------------------------------------
2356 - def OnClose(self, event):
2357 """This is the wx.EVT_CLOSE handler. 2358 2359 - framework still functional 2360 """ 2361 _log.debug('gmTopLevelFrame.OnClose() start') 2362 self._clean_exit() 2363 self.Destroy() 2364 _log.debug('gmTopLevelFrame.OnClose() end') 2365 return True
2366 #----------------------------------------------
2367 - def OnExportEMR(self, event):
2368 """ 2369 Export selected patient EMR to a file 2370 """ 2371 gmEMRBrowser.export_emr_to_ascii(parent=self)
2372 #----------------------------------------------
2373 - def __dermtool (self, event):
2374 import Gnumed.wxpython.gmDermTool as DT 2375 frame = DT.DermToolDialog(None, -1) 2376 frame.Show(True)
2377 #----------------------------------------------
2378 - def __on_start_new_encounter(self, evt):
2379 pat = gmPerson.gmCurrentPatient() 2380 if not pat.connected: 2381 gmDispatcher.send(signal = 'statustext', msg = _('Cannot start new encounter. No active patient.')) 2382 return False 2383 emr = pat.get_emr() 2384 gmEMRStructWidgets.start_new_encounter(emr = emr)
2385 #----------------------------------------------
2386 - def __on_list_encounters(self, evt):
2387 pat = gmPerson.gmCurrentPatient() 2388 if not pat.connected: 2389 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.')) 2390 return False 2391 gmEMRStructWidgets.select_encounters()
2392 #----------------------------------------------
2393 - def __on_add_health_issue(self, event):
2394 pat = gmPerson.gmCurrentPatient() 2395 if not pat.connected: 2396 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add health issue. No active patient.')) 2397 return False 2398 gmEMRStructWidgets.edit_health_issue(parent = self, issue = None)
2399 #----------------------------------------------
2400 - def __on_add_medication(self, evt):
2401 pat = gmPerson.gmCurrentPatient() 2402 if not pat.connected: 2403 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add medication. No active patient.')) 2404 return False 2405 2406 gmMedicationWidgets.edit_intake_of_substance(parent = self, substance = None) 2407 2408 evt.Skip()
2409 #----------------------------------------------
2410 - def __on_manage_allergies(self, evt):
2411 pat = gmPerson.gmCurrentPatient() 2412 if not pat.connected: 2413 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add allergy. No active patient.')) 2414 return False 2415 dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1) 2416 dlg.ShowModal()
2417 #----------------------------------------------
2418 - def __on_manage_performed_procedures(self, evt):
2419 pat = gmPerson.gmCurrentPatient() 2420 if not pat.connected: 2421 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage performed procedures. No active patient.')) 2422 return False 2423 gmEMRStructWidgets.manage_performed_procedures(parent = self) 2424 evt.Skip()
2425 #----------------------------------------------
2426 - def __on_manage_hospital_stays(self, evt):
2427 pat = gmPerson.gmCurrentPatient() 2428 if not pat.connected: 2429 gmDispatcher.send(signal = 'statustext', msg = _('Cannot manage hospital stays. No active patient.')) 2430 return False 2431 gmEMRStructWidgets.manage_hospital_stays(parent = self) 2432 evt.Skip()
2433 #----------------------------------------------
2434 - def __on_edit_occupation(self, evt):
2435 pat = gmPerson.gmCurrentPatient() 2436 if not pat.connected: 2437 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit occupation. No active patient.')) 2438 return False 2439 gmDemographicsWidgets.edit_occupation() 2440 evt.Skip()
2441 #----------------------------------------------
2442 - def __on_add_vaccination(self, evt):
2443 pat = gmPerson.gmCurrentPatient() 2444 if not pat.connected: 2445 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add vaccinations. No active patient.')) 2446 return False 2447 2448 gmVaccWidgets.manage_vaccinations(parent = self) 2449 evt.Skip()
2450 #----------------------------------------------
2451 - def __on_add_measurement(self, evt):
2452 pat = gmPerson.gmCurrentPatient() 2453 if not pat.connected: 2454 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add measurement. No active patient.')) 2455 return False 2456 gmMeasurementWidgets.edit_measurement(parent = self, measurement = None) 2457 evt.Skip()
2458 #----------------------------------------------
2459 - def __on_show_emr_summary(self, event):
2460 pat = gmPerson.gmCurrentPatient() 2461 if not pat.connected: 2462 gmDispatcher.send(signal = 'statustext', msg = _('Cannot show EMR summary. No active patient.')) 2463 return False 2464 2465 emr = pat.get_emr() 2466 dlg = wx.MessageDialog ( 2467 parent = self, 2468 message = emr.format_statistics(), 2469 caption = _('EMR Summary'), 2470 style = wx.OK | wx.STAY_ON_TOP 2471 ) 2472 dlg.ShowModal() 2473 dlg.Destroy() 2474 return True
2475 #----------------------------------------------
2476 - def __on_search_emr(self, event):
2477 return gmNarrativeWidgets.search_narrative_in_emr(parent=self)
2478 #----------------------------------------------
2479 - def __on_search_across_emrs(self, event):
2480 gmNarrativeWidgets.search_narrative_across_emrs(parent=self)
2481 #----------------------------------------------
2482 - def __on_export_emr_as_journal(self, event):
2483 # sanity checks 2484 pat = gmPerson.gmCurrentPatient() 2485 if not pat.connected: 2486 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.')) 2487 return False 2488 # get file name 2489 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files")) 2490 # FIXME: make configurable 2491 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname'])) 2492 gmTools.mkdir(aDefDir) 2493 # FIXME: make configurable 2494 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames']) 2495 dlg = wx.FileDialog ( 2496 parent = self, 2497 message = _("Save patient's EMR journal as..."), 2498 defaultDir = aDefDir, 2499 defaultFile = fname, 2500 wildcard = aWildcard, 2501 style = wx.SAVE 2502 ) 2503 choice = dlg.ShowModal() 2504 fname = dlg.GetPath() 2505 dlg.Destroy() 2506 if choice != wx.ID_OK: 2507 return True 2508 2509 _log.debug('exporting EMR journal to [%s]' % fname) 2510 # instantiate exporter 2511 exporter = gmPatientExporter.cEMRJournalExporter() 2512 2513 wx.BeginBusyCursor() 2514 try: 2515 fname = exporter.export_to_file(filename = fname) 2516 except: 2517 wx.EndBusyCursor() 2518 gmGuiHelpers.gm_show_error ( 2519 _('Error exporting patient EMR as chronological journal.'), 2520 _('EMR journal export') 2521 ) 2522 raise 2523 wx.EndBusyCursor() 2524 2525 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False) 2526 2527 return True
2528 #----------------------------------------------
2529 - def __on_export_for_medistar(self, event):
2530 gmNarrativeWidgets.export_narrative_for_medistar_import ( 2531 parent = self, 2532 soap_cats = u'soap', 2533 encounter = None # IOW, the current one 2534 )
2535 #----------------------------------------------
2536 - def __on_add_tag2person(self, event):
2537 curr_pat = gmPerson.gmCurrentPatient() 2538 if not curr_pat.connected: 2539 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.')) 2540 return 2541 2542 tag = gmDemographicsWidgets.manage_tag_images(parent = self) 2543 if tag is None: 2544 return 2545 2546 tag = curr_pat.add_tag(tag['pk_tag_image']) 2547 msg = _('Edit the comment on tag [%s]') % tag['l10n_description'] 2548 comment = wx.GetTextFromUser ( 2549 message = msg, 2550 caption = _('Editing tag comment'), 2551 default_value = gmTools.coalesce(tag['comment'], u''), 2552 parent = self 2553 ) 2554 2555 if comment == u'': 2556 return 2557 2558 if comment.strip() == tag['comment']: 2559 return 2560 2561 if comment == u' ': 2562 tag['comment'] = None 2563 else: 2564 tag['comment'] = comment.strip() 2565 2566 tag.save()
2567 #----------------------------------------------
2568 - def __on_load_external_patient(self, event):
2569 dbcfg = gmCfg.cCfgSQL() 2570 search_immediately = bool(dbcfg.get2 ( 2571 option = 'patient_search.external_sources.immediately_search_if_single_source', 2572 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2573 bias = 'user', 2574 default = 0 2575 )) 2576 gmPatSearchWidgets.get_person_from_external_sources(parent=self, search_immediately=search_immediately, activate_immediately=True)
2577 #----------------------------------------------
2578 - def __on_export_as_gdt(self, event):
2579 curr_pat = gmPerson.gmCurrentPatient() 2580 if not curr_pat.connected: 2581 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.')) 2582 return False 2583 # FIXME: configurable 2584 enc = 'cp850' 2585 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt')) 2586 curr_pat.export_as_gdt(filename = fname, encoding = enc) 2587 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2588 #----------------------------------------------
2589 - def __on_create_new_patient(self, evt):
2590 gmDemographicsWidgets.create_new_person(parent = self, activate = True)
2591 #----------------------------------------------
2592 - def __on_enlist_patient_as_staff(self, event):
2593 pat = gmPerson.gmCurrentPatient() 2594 if not pat.connected: 2595 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add staff member. No active patient.')) 2596 return False 2597 dlg = gmStaffWidgets.cAddPatientAsStaffDlg(parent=self, id=-1) 2598 dlg.ShowModal()
2599 #----------------------------------------------
2600 - def __on_delete_patient(self, event):
2601 pat = gmPerson.gmCurrentPatient() 2602 if not pat.connected: 2603 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete patient. No patient active.')) 2604 return False 2605 gmDemographicsWidgets.disable_identity(identity=pat) 2606 return True
2607 #----------------------------------------------
2608 - def __on_merge_patients(self, event):
2609 gmPatSearchWidgets.merge_patients(parent=self)
2610 #----------------------------------------------
2611 - def __on_add_new_staff(self, event):
2612 """Create new person and add it as staff.""" 2613 if not gmDemographicsWidgets.create_new_person(parent = self, activate = True): 2614 return 2615 dlg = gmStaffWidgets.cAddPatientAsStaffDlg(parent=self, id=-1) 2616 dlg.ShowModal()
2617 #----------------------------------------------
2618 - def __on_edit_staff_list(self, event):
2619 dlg = gmStaffWidgets.cEditStaffListDlg(parent=self, id=-1) 2620 dlg.ShowModal()
2621 #----------------------------------------------
2622 - def __on_edit_gmdbowner_password(self, evt):
2623 gmAuthWidgets.change_gmdbowner_password()
2624 #----------------------------------------------
2625 - def __on_update_loinc(self, evt):
2626 gmMeasurementWidgets.update_loinc_reference_data()
2627 #----------------------------------------------
2628 - def __on_update_atc(self, evt):
2629 gmMedicationWidgets.update_atc_reference_data()
2630 #----------------------------------------------
2631 - def __on_generate_vaccines(self, evt):
2632 wx.BeginBusyCursor() 2633 gmVaccination.regenerate_generic_vaccines() 2634 wx.EndBusyCursor()
2635 #----------------------------------------------
2636 - def _clean_exit(self):
2637 """Cleanup helper. 2638 2639 - should ALWAYS be called when this program is 2640 to be terminated 2641 - ANY code that should be executed before a 2642 regular shutdown should go in here 2643 - framework still functional 2644 """ 2645 _log.debug('gmTopLevelFrame._clean_exit() start') 2646 2647 # shut down backend notifications listener 2648 listener = gmBackendListener.gmBackendListener() 2649 try: 2650 listener.shutdown() 2651 except: 2652 _log.exception('cannot stop backend notifications listener thread') 2653 2654 # shutdown application scripting listener 2655 if _scripting_listener is not None: 2656 try: 2657 _scripting_listener.shutdown() 2658 except: 2659 _log.exception('cannot stop scripting listener thread') 2660 2661 # shutdown timers 2662 self.clock_update_timer.Stop() 2663 gmTimer.shutdown() 2664 gmPhraseWheel.shutdown() 2665 2666 # run synchronous pre-exit callback 2667 for call_back in self.__pre_exit_callbacks: 2668 try: 2669 call_back() 2670 except: 2671 print "*** pre-exit callback failed ***" 2672 print call_back 2673 _log.exception('callback [%s] failed', call_back) 2674 2675 # signal imminent demise to plugins 2676 gmDispatcher.send(u'application_closing') 2677 2678 # do not show status line messages anymore 2679 gmDispatcher.disconnect(self._on_set_statustext, 'statustext') 2680 2681 # remember GUI size 2682 curr_width, curr_height = self.GetClientSizeTuple() 2683 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height)) 2684 dbcfg = gmCfg.cCfgSQL() 2685 dbcfg.set ( 2686 option = 'main.window.width', 2687 value = curr_width, 2688 workplace = gmSurgery.gmCurrentPractice().active_workplace 2689 ) 2690 dbcfg.set ( 2691 option = 'main.window.height', 2692 value = curr_height, 2693 workplace = gmSurgery.gmCurrentPractice().active_workplace 2694 ) 2695 2696 if _cfg.get(option = 'debug'): 2697 print '---=== GNUmed shutdown ===---' 2698 try: 2699 print _('You have to manually close this window to finalize shutting down GNUmed.') 2700 print _('This is so that you can inspect the console output at your leisure.') 2701 except UnicodeEncodeError: 2702 print 'You have to manually close this window to finalize shutting down GNUmed.' 2703 print 'This is so that you can inspect the console output at your leisure.' 2704 print '---=== GNUmed shutdown ===---' 2705 2706 # shutdown GUI exception handling 2707 gmExceptionHandlingWidgets.uninstall_wx_exception_handler() 2708 2709 # are we clean ? 2710 import threading 2711 _log.debug("%s active threads", threading.activeCount()) 2712 for t in threading.enumerate(): 2713 _log.debug('thread %s', t) 2714 2715 _log.debug('gmTopLevelFrame._clean_exit() end')
2716 #---------------------------------------------- 2717 # internal API 2718 #----------------------------------------------
2719 - def __set_window_title_template(self):
2720 2721 if _cfg.get(option = 'slave'): 2722 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % ( 2723 _cfg.get(option = 'slave personality'), 2724 _cfg.get(option = 'xml-rpc port') 2725 ) 2726 else: 2727 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2728 #----------------------------------------------
2729 - def __update_window_title(self):
2730 """Update title of main window based on template. 2731 2732 This gives nice tooltips on iconified GNUmed instances. 2733 2734 User research indicates that in the title bar people want 2735 the date of birth, not the age, so please stick to this 2736 convention. 2737 """ 2738 args = {} 2739 2740 pat = gmPerson.gmCurrentPatient() 2741 if pat.connected: 2742 args['pat'] = u'%s %s %s (%s) #%d' % ( 2743 gmTools.coalesce(pat['title'], u'', u'%.4s'), 2744 pat['firstnames'], 2745 pat['lastnames'], 2746 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()), 2747 pat['pk_identity'] 2748 ) 2749 else: 2750 args['pat'] = _('no patient') 2751 2752 args['prov'] = u'%s%s.%s' % ( 2753 gmTools.coalesce(_provider['title'], u'', u'%s '), 2754 _provider['firstnames'][:1], 2755 _provider['lastnames'] 2756 ) 2757 2758 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace 2759 2760 self.SetTitle(self.__title_template % args)
2761 #---------------------------------------------- 2762 #----------------------------------------------
2763 - def setup_statusbar(self):
2764 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP) 2765 sb.SetStatusWidths([-1, 225]) 2766 # add time and date display to the right corner of the status bar 2767 self.clock_update_timer = wx.PyTimer(self._cb_update_clock) 2768 self._cb_update_clock() 2769 # update every second 2770 self.clock_update_timer.Start(milliseconds = 1000)
2771 #----------------------------------------------
2772 - def _cb_update_clock(self):
2773 """Displays date and local time in the second slot of the status bar""" 2774 t = time.localtime(time.time()) 2775 st = time.strftime('%c', t).decode(gmI18N.get_encoding()) 2776 self.SetStatusText(st,1)
2777 #------------------------------------------------
2778 - def Lock(self):
2779 """Lock GNUmed client against unauthorized access""" 2780 # FIXME 2781 # for i in range(1, self.nb.GetPageCount()): 2782 # self.nb.GetPage(i).Enable(False) 2783 return
2784 #----------------------------------------------
2785 - def Unlock(self):
2786 """Unlock the main notebook widgets 2787 As long as we are not logged into the database backend, 2788 all pages but the 'login' page of the main notebook widget 2789 are locked; i.e. not accessible by the user 2790 """ 2791 #unlock notebook pages 2792 # for i in range(1, self.nb.GetPageCount()): 2793 # self.nb.GetPage(i).Enable(True) 2794 # go straight to patient selection 2795 # self.nb.AdvanceSelection() 2796 return
2797 #-----------------------------------------------
2798 - def OnPanelSize (self, event):
2799 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2800 #==============================================================================
2801 -class gmApp(wx.App):
2802
2803 - def OnInit(self):
2804 2805 self.__starting_up = True 2806 2807 gmExceptionHandlingWidgets.install_wx_exception_handler() 2808 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version')) 2809 2810 # _log.info('display: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y))) 2811 2812 # set this so things like "wx.StandardPaths.GetDataDir()" work as expected 2813 self.SetAppName(u'gnumed') 2814 self.SetVendorName(u'The GNUmed Development Community.') 2815 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx) 2816 paths.init_paths(wx = wx, app_name = u'gnumed') 2817 2818 if not self.__setup_prefs_file(): 2819 return False 2820 2821 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email) 2822 2823 self.__guibroker = gmGuiBroker.GuiBroker() 2824 self.__setup_platform() 2825 2826 if not self.__establish_backend_connection(): 2827 return False 2828 2829 if not _cfg.get(option = 'skip-update-check'): 2830 self.__check_for_updates() 2831 2832 if _cfg.get(option = 'slave'): 2833 if not self.__setup_scripting_listener(): 2834 return False 2835 2836 # FIXME: load last position from backend 2837 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440)) 2838 frame.CentreOnScreen(wx.BOTH) 2839 self.SetTopWindow(frame) 2840 frame.Show(True) 2841 2842 if _cfg.get(option = 'debug'): 2843 self.RedirectStdio() 2844 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window')) 2845 # print this so people know what this window is for 2846 # and don't get suprised when it pops up later 2847 print '---=== GNUmed startup ===---' 2848 print _('redirecting STDOUT/STDERR to this log window') 2849 print '---=== GNUmed startup ===---' 2850 2851 self.__setup_user_activity_timer() 2852 self.__register_events() 2853 2854 wx.CallAfter(self._do_after_init) 2855 2856 return True
2857 #----------------------------------------------
2858 - def OnExit(self):
2859 """Called internally by wxPython after EVT_CLOSE has been handled on last frame. 2860 2861 - after destroying all application windows and controls 2862 - before wx.Windows internal cleanup 2863 """ 2864 _log.debug('gmApp.OnExit() start') 2865 2866 self.__shutdown_user_activity_timer() 2867 2868 if _cfg.get(option = 'debug'): 2869 self.RestoreStdio() 2870 sys.stdin = sys.__stdin__ 2871 sys.stdout = sys.__stdout__ 2872 sys.stderr = sys.__stderr__ 2873 2874 _log.debug('gmApp.OnExit() end')
2875 #----------------------------------------------
2876 - def _on_query_end_session(self, *args, **kwargs):
2877 wx.Bell() 2878 wx.Bell() 2879 wx.Bell() 2880 _log.warning('unhandled event detected: QUERY_END_SESSION') 2881 _log.info('we should be saving ourselves from here') 2882 gmLog2.flush() 2883 print "unhandled event detected: QUERY_END_SESSION"
2884 #----------------------------------------------
2885 - def _on_end_session(self, *args, **kwargs):
2886 wx.Bell() 2887 wx.Bell() 2888 wx.Bell() 2889 _log.warning('unhandled event detected: END_SESSION') 2890 gmLog2.flush() 2891 print "unhandled event detected: END_SESSION"
2892 #----------------------------------------------
2893 - def _on_app_activated(self, evt):
2894 if evt.GetActive(): 2895 if self.__starting_up: 2896 gmHooks.run_hook_script(hook = u'app_activated_startup') 2897 else: 2898 gmHooks.run_hook_script(hook = u'app_activated') 2899 else: 2900 gmHooks.run_hook_script(hook = u'app_deactivated') 2901 2902 evt.Skip()
2903 #----------------------------------------------
2904 - def _on_user_activity(self, evt):
2905 self.user_activity_detected = True 2906 evt.Skip()
2907 #----------------------------------------------
2908 - def _on_user_activity_timer_expired(self, cookie=None):
2909 2910 if self.user_activity_detected: 2911 self.elapsed_inactivity_slices = 0 2912 self.user_activity_detected = False 2913 self.elapsed_inactivity_slices += 1 2914 else: 2915 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices: 2916 # print "User was inactive for 30 seconds." 2917 pass 2918 2919 self.user_activity_timer.Start(oneShot = True)
2920 #---------------------------------------------- 2921 # internal helpers 2922 #----------------------------------------------
2923 - def _signal_debugging_monitor(*args, **kwargs):
2924 try: 2925 kwargs['originated_in_database'] 2926 print '==> got notification from database "%s":' % kwargs['signal'] 2927 except KeyError: 2928 print '==> received signal from client: "%s"' % kwargs['signal'] 2929 2930 del kwargs['signal'] 2931 for key in kwargs.keys(): 2932 print ' [%s]: %s' % (key, kwargs[key])
2933 #----------------------------------------------
2934 - def _signal_debugging_monitor_pubsub(self, msg):
2935 print "wx.lib.pubsub message:" 2936 print msg.topic 2937 print msg.data
2938 #----------------------------------------------
2939 - def _do_after_init(self):
2940 self.__starting_up = False 2941 gmClinicalRecord.set_func_ask_user(a_func = gmEMRStructWidgets.ask_for_encounter_continuation) 2942 self.__guibroker['horstspace.top_panel'].patient_selector.SetFocus() 2943 gmHooks.run_hook_script(hook = u'startup-after-GUI-init')
2944 #----------------------------------------------
2946 self.user_activity_detected = True 2947 self.elapsed_inactivity_slices = 0 2948 # FIXME: make configurable 2949 self.max_user_inactivity_slices = 15 # 15 * 2000ms == 30 seconds 2950 self.user_activity_timer = gmTimer.cTimer ( 2951 callback = self._on_user_activity_timer_expired, 2952 delay = 2000 # hence a minimum of 2 and max of 3.999... seconds after which inactivity is detected 2953 ) 2954 self.user_activity_timer.Start(oneShot=True)
2955 #----------------------------------------------
2957 try: 2958 self.user_activity_timer.Stop() 2959 del self.user_activity_timer 2960 except: 2961 pass
2962 #----------------------------------------------
2963 - def __register_events(self):
2964 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session) 2965 wx.EVT_END_SESSION(self, self._on_end_session) 2966 2967 # You can bind your app to wx.EVT_ACTIVATE_APP which will fire when your 2968 # app gets/looses focus, or you can wx.EVT_ACTIVATE with any of your 2969 # toplevel windows and call evt.GetActive() in the handler to see whether 2970 # it is gaining or loosing focus. 2971 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated) 2972 2973 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity) 2974 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity) 2975 2976 if _cfg.get(option = 'debug'): 2977 gmDispatcher.connect(receiver = self._signal_debugging_monitor) 2978 _log.debug('connected old signal monitor') 2979 wx.lib.pubsub.Publisher().subscribe(listener = self._signal_debugging_monitor_pubsub) 2980 _log.debug('connected wx.lib.pubsub based signal monitor for all topics')
2981 2982 # wx.lib.pubsub.Publisher().subscribe ( 2983 # listener = self._signal_debugging_monitor_pubsub, 2984 # topic = wx.lib.pubsub.getStrAllTopics() # does not exist anymore in later versions of pubsub 2985 # ) 2986 #----------------------------------------------
2987 - def __check_for_updates(self):
2988 2989 dbcfg = gmCfg.cCfgSQL() 2990 2991 do_check = bool(dbcfg.get2 ( 2992 option = u'horstspace.update.autocheck_at_startup', 2993 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2994 bias = 'workplace', 2995 default = True 2996 )) 2997 2998 if not do_check: 2999 return 3000 3001 gmCfgWidgets.check_for_updates()
3002 #----------------------------------------------
3004 """Handle all the database related tasks necessary for startup.""" 3005 3006 # log on 3007 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')]) 3008 3009 from Gnumed.wxpython import gmAuthWidgets 3010 connected = gmAuthWidgets.connect_to_database ( 3011 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')], 3012 require_version = not override 3013 ) 3014 if not connected: 3015 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection") 3016 return False 3017 3018 # check account <-> staff member association 3019 try: 3020 global _provider 3021 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff()) 3022 except ValueError: 3023 account = gmPG2.get_current_user() 3024 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account) 3025 msg = _( 3026 'The database account [%s] cannot be used as a\n' 3027 'staff member login for GNUmed. There was an\n' 3028 'error retrieving staff details for it.\n\n' 3029 'Please ask your administrator for help.\n' 3030 ) % account 3031 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions')) 3032 return False 3033 3034 # improve exception handler setup 3035 tmp = '%s%s %s (%s = %s)' % ( 3036 gmTools.coalesce(_provider['title'], ''), 3037 _provider['firstnames'], 3038 _provider['lastnames'], 3039 _provider['short_alias'], 3040 _provider['db_user'] 3041 ) 3042 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp) 3043 3044 # display database banner 3045 surgery = gmSurgery.gmCurrentPractice() 3046 msg = surgery.db_logon_banner 3047 if msg.strip() != u'': 3048 3049 login = gmPG2.get_default_login() 3050 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % ( 3051 login.database, 3052 gmTools.coalesce(login.host, u'localhost') 3053 )) 3054 msg = auth + msg + u'\n\n' 3055 3056 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 3057 None, 3058 #self.GetTopWindow(), # freezes 3059 -1, 3060 caption = _('Verifying database'), 3061 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '), 3062 button_defs = [ 3063 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True}, 3064 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False} 3065 ] 3066 ) 3067 go_on = dlg.ShowModal() 3068 dlg.Destroy() 3069 if go_on != wx.ID_YES: 3070 _log.info('user decided to not connect to this database') 3071 return False 3072 3073 # check database language settings 3074 self.__check_db_lang() 3075 3076 return True
3077 #----------------------------------------------
3078 - def __setup_prefs_file(self):
3079 """Setup access to a config file for storing preferences.""" 3080 3081 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx) 3082 3083 candidates = [] 3084 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')]) 3085 if explicit_file is not None: 3086 candidates.append(explicit_file) 3087 # provide a few fallbacks in the event the --conf-file isn't writable 3088 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf')) 3089 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf')) 3090 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf')) 3091 3092 prefs_file = None 3093 for candidate in candidates: 3094 try: 3095 open(candidate, 'a+').close() 3096 prefs_file = candidate 3097 break 3098 except IOError: 3099 continue 3100 3101 if prefs_file is None: 3102 msg = _( 3103 'Cannot find configuration file in any of:\n' 3104 '\n' 3105 ' %s\n' 3106 'You may need to use the comand line option\n' 3107 '\n' 3108 ' --conf-file=<FILE>' 3109 ) % '\n '.join(candidates) 3110 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files')) 3111 return False 3112 3113 _cfg.set_option(option = u'user_preferences_file', value = prefs_file) 3114 _log.info('user preferences file: %s', prefs_file) 3115 3116 return True
3117 #----------------------------------------------
3118 - def __setup_scripting_listener(self):
3119 3120 from socket import error as SocketError 3121 from Gnumed.pycommon import gmScriptingListener 3122 from Gnumed.wxpython import gmMacro 3123 3124 slave_personality = gmTools.coalesce ( 3125 _cfg.get ( 3126 group = u'workplace', 3127 option = u'slave personality', 3128 source_order = [ 3129 ('explicit', 'return'), 3130 ('workbase', 'return'), 3131 ('user', 'return'), 3132 ('system', 'return') 3133 ] 3134 ), 3135 u'gnumed-client' 3136 ) 3137 _cfg.set_option(option = 'slave personality', value = slave_personality) 3138 3139 # FIXME: handle port via /var/run/ 3140 port = int ( 3141 gmTools.coalesce ( 3142 _cfg.get ( 3143 group = u'workplace', 3144 option = u'xml-rpc port', 3145 source_order = [ 3146 ('explicit', 'return'), 3147 ('workbase', 'return'), 3148 ('user', 'return'), 3149 ('system', 'return') 3150 ] 3151 ), 3152 9999 3153 ) 3154 ) 3155 _cfg.set_option(option = 'xml-rpc port', value = port) 3156 3157 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality) 3158 global _scripting_listener 3159 try: 3160 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor) 3161 except SocketError, e: 3162 _log.exception('cannot start GNUmed XML-RPC server') 3163 gmGuiHelpers.gm_show_error ( 3164 aMessage = ( 3165 'Cannot start the GNUmed server:\n' 3166 '\n' 3167 ' [%s]' 3168 ) % e, 3169 aTitle = _('GNUmed startup') 3170 ) 3171 return False 3172 3173 return True
3174 #----------------------------------------------
3175 - def __setup_platform(self):
3176 3177 import wx.lib.colourdb 3178 wx.lib.colourdb.updateColourDB() 3179 3180 traits = self.GetTraits() 3181 try: 3182 _log.info('desktop environment: [%s]', traits.GetDesktopEnvironment()) 3183 except: 3184 pass 3185 3186 if wx.Platform == '__WXMSW__': 3187 _log.info('running on MS Windows') 3188 elif wx.Platform == '__WXGTK__': 3189 _log.info('running on GTK (probably Linux)') 3190 elif wx.Platform == '__WXMAC__': 3191 _log.info('running on Mac OS') 3192 wx.SystemOptions.SetOptionInt('mac.textcontrol-use-spell-checker', 1) 3193 else: 3194 _log.info('running on an unknown platform (%s)' % wx.Platform)
3195 #----------------------------------------------
3196 - def __check_db_lang(self):
3197 if gmI18N.system_locale is None or gmI18N.system_locale == '': 3198 _log.warning("system locale is undefined (probably meaning 'C')") 3199 return True 3200 3201 # get current database locale 3202 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}]) 3203 db_lang = rows[0]['lang'] 3204 3205 if db_lang is None: 3206 _log.debug("database locale currently not set") 3207 msg = _( 3208 "There is no language selected in the database for user [%s].\n" 3209 "Your system language is currently set to [%s].\n\n" 3210 "Do you want to set the database language to '%s' ?\n\n" 3211 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale) 3212 checkbox_msg = _('Remember to ignore missing language') 3213 else: 3214 _log.debug("current database locale: [%s]" % db_lang) 3215 msg = _( 3216 "The currently selected database language ('%s') does\n" 3217 "not match the current system language ('%s').\n" 3218 "\n" 3219 "Do you want to set the database language to '%s' ?\n" 3220 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale) 3221 checkbox_msg = _('Remember to ignore language mismatch') 3222 3223 # check if we can match up system and db language somehow 3224 if db_lang == gmI18N.system_locale_level['full']: 3225 _log.debug('Database locale (%s) up to date.' % db_lang) 3226 return True 3227 if db_lang == gmI18N.system_locale_level['country']: 3228 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale)) 3229 return True 3230 if db_lang == gmI18N.system_locale_level['language']: 3231 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale)) 3232 return True 3233 # no match 3234 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale)) 3235 3236 # returns either None or a locale string 3237 ignored_sys_lang = _cfg.get ( 3238 group = u'backend', 3239 option = u'ignored mismatching system locale', 3240 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')] 3241 ) 3242 3243 # are we to ignore *this* mismatch ? 3244 if gmI18N.system_locale == ignored_sys_lang: 3245 _log.info('configured to ignore system-to-database locale mismatch') 3246 return True 3247 3248 # no, so ask user 3249 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 3250 None, 3251 -1, 3252 caption = _('Checking database language settings'), 3253 question = msg, 3254 button_defs = [ 3255 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True}, 3256 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False} 3257 ], 3258 show_checkbox = True, 3259 checkbox_msg = checkbox_msg, 3260 checkbox_tooltip = _( 3261 'Checking this will make GNUmed remember your decision\n' 3262 'until the system language is changed.\n' 3263 '\n' 3264 'You can also reactivate this inquiry by removing the\n' 3265 'corresponding "ignore" option from the configuration file\n' 3266 '\n' 3267 ' [%s]' 3268 ) % _cfg.get(option = 'user_preferences_file') 3269 ) 3270 decision = dlg.ShowModal() 3271 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue() 3272 dlg.Destroy() 3273 3274 if decision == wx.ID_NO: 3275 if not remember_ignoring_problem: 3276 return True 3277 _log.info('User did not want to set database locale. Ignoring mismatch next time.') 3278 gmCfg2.set_option_in_INI_file ( 3279 filename = _cfg.get(option = 'user_preferences_file'), 3280 group = 'backend', 3281 option = 'ignored mismatching system locale', 3282 value = gmI18N.system_locale 3283 ) 3284 return True 3285 3286 # try setting database language (only possible if translation exists) 3287 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]: 3288 if len(lang) > 0: 3289 # users are getting confused, so don't show these "errors", 3290 # they really are just notices about us being nice 3291 rows, idx = gmPG2.run_rw_queries ( 3292 link_obj = None, 3293 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}], 3294 return_data = True 3295 ) 3296 if rows[0][0]: 3297 _log.debug("Successfully set database language to [%s]." % lang) 3298 else: 3299 _log.error('Cannot set database language to [%s].' % lang) 3300 continue 3301 return True 3302 3303 # no match found but user wanted to set language anyways, so force it 3304 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country']) 3305 gmPG2.run_rw_queries(queries = [{ 3306 'cmd': u'select i18n.force_curr_lang(%s)', 3307 'args': [gmI18N.system_locale_level['country']] 3308 }]) 3309 3310 return True
3311 #==============================================================================
3312 -def _signal_debugging_monitor(*args, **kwargs):
3313 try: 3314 kwargs['originated_in_database'] 3315 print '==> got notification from database "%s":' % kwargs['signal'] 3316 except KeyError: 3317 print '==> received signal from client: "%s"' % kwargs['signal'] 3318 3319 del kwargs['signal'] 3320 for key in kwargs.keys(): 3321 # careful because of possibly limited console output encoding 3322 try: print ' [%s]: %s' % (key, kwargs[key]) 3323 except: print 'cannot print signal information'
3324 #------------------------------------------------------------------------------
3325 -def _signal_debugging_monitor_pubsub(msg):
3326 # careful because of possibly limited console output encoding 3327 try: 3328 print '==> received wx.lib.pubsub message: "%s"' % msg.topic 3329 print ' data: %s' % msg.data 3330 print msg 3331 except: print 'problem printing pubsub message information'
3332 #==============================================================================
3333 -def main():
3334 3335 if _cfg.get(option = 'debug'): 3336 gmDispatcher.connect(receiver = _signal_debugging_monitor) 3337 _log.debug('gmDispatcher signal monitor activated') 3338 wx.lib.pubsub.Publisher().subscribe ( 3339 listener = _signal_debugging_monitor_pubsub 3340 # , topic = wx.lib.pubsub.getStrAllTopics() # not available in some implementations 3341 ) 3342 _log.debug('wx.lib.pubsub signal monitor activated') 3343 3344 wx.InitAllImageHandlers() 3345 # create an instance of our GNUmed main application 3346 # - do not redirect stdio (yet) 3347 # - allow signals to be delivered 3348 app = gmApp(redirect = False, clearSigInt = False) 3349 app.MainLoop()
3350 #============================================================================== 3351 # Main 3352 #============================================================================== 3353 if __name__ == '__main__': 3354 3355 from GNUmed.pycommon import gmI18N 3356 gmI18N.activate_locale() 3357 gmI18N.install_domain() 3358 3359 _log.info('Starting up as main module.') 3360 main() 3361 3362 #============================================================================== 3363