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