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

Source Code for Module Gnumed.wxpython.gmGuiMain

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