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