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