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