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