1
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 This source code is protected by the GPL licensing scheme.
10 Details regarding the GPL are available at http://www.gnu.org
11 You may use and share it as long as you don't deny this right
12 to anybody else.
13
14 copyright: authors
15 """
16
17 __version__ = "$Revision: 1.491 $"
18 __author__ = "H. Herb <hherb@gnumed.net>,\
19 K. Hilbert <Karsten.Hilbert@gmx.net>,\
20 I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
21 __license__ = 'GPL (details at http://www.gnu.org)'
22
23
24 import sys, time, os, locale, os.path, datetime as pyDT
25 import webbrowser, shutil, logging, urllib2, subprocess, glob
26
27
28
29
30 if not hasattr(sys, 'frozen'):
31 import wxversion
32 wxversion.ensureMinimal('2.8-unicode', optionsRequired=True)
33
34 try:
35 import wx
36 import wx.lib.pubsub
37 except ImportError:
38 print "GNUmed startup: Cannot import wxPython library."
39 print "GNUmed startup: Make sure wxPython is installed."
40 print 'CRITICAL ERROR: Error importing wxPython. Halted.'
41 raise
42
43
44
45 version = int(u'%s%s' % (wx.MAJOR_VERSION, wx.MINOR_VERSION))
46 if (version < 28) or ('unicode' not in wx.PlatformInfo):
47 print "GNUmed startup: Unsupported wxPython version (%s: %s)." % (wx.VERSION_STRING, wx.PlatformInfo)
48 print "GNUmed startup: wxPython 2.8+ with unicode support is required."
49 print 'CRITICAL ERROR: Proper wxPython version not found. Halted.'
50 raise ValueError('wxPython 2.8+ with unicode support not found')
51
52
53
54 from Gnumed.pycommon import gmCfg, gmPG2, gmDispatcher, gmGuiBroker, gmI18N
55 from Gnumed.pycommon import gmExceptions, gmShellAPI, gmTools, gmDateTime
56 from Gnumed.pycommon import gmHooks, gmBackendListener, gmCfg2, gmLog2
57
58 from Gnumed.business import gmPerson, gmClinicalRecord, gmSurgery, gmEMRStructItems
59 from Gnumed.business import gmVaccination
60
61 from Gnumed.exporters import gmPatientExporter
62
63 from Gnumed.wxpython import gmGuiHelpers, gmHorstSpace, gmEMRBrowser
64 from Gnumed.wxpython import gmDemographicsWidgets, gmEMRStructWidgets
65 from Gnumed.wxpython import gmPatSearchWidgets, gmAllergyWidgets, gmListWidgets
66 from Gnumed.wxpython import gmProviderInboxWidgets, gmCfgWidgets, gmExceptionHandlingWidgets
67 from Gnumed.wxpython import gmNarrativeWidgets, gmPhraseWheel, gmMedicationWidgets
68 from Gnumed.wxpython import gmStaffWidgets, gmDocumentWidgets, gmTimer, gmMeasurementWidgets
69 from Gnumed.wxpython import gmFormWidgets, gmSnellen, gmVaccWidgets, gmPersonContactWidgets
70
71 try:
72 _('dummy-no-need-to-translate-but-make-epydoc-happy')
73 except NameError:
74 _ = lambda x:x
75
76 _cfg = gmCfg2.gmCfgData()
77 _provider = None
78 _scripting_listener = None
79
80 _log = logging.getLogger('gm.main')
81 _log.info(__version__)
82 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
83
84
86 """GNUmed client's main windows frame.
87
88 This is where it all happens. Avoid popping up any other windows.
89 Most user interaction should happen to and from widgets within this frame
90 """
91
92 - def __init__(self, parent, id, title, size=wx.DefaultSize):
93 """You'll have to browse the source to understand what the constructor does
94 """
95 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
96
97 if wx.Platform == '__WXMSW__':
98 font = self.GetFont()
99 _log.debug('default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
100 desired_font_face = u'DejaVu Sans'
101 success = font.SetFaceName(desired_font_face)
102 if success:
103 self.SetFont(font)
104 _log.debug('setting font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
105 else:
106 font = self.GetFont()
107 _log.error('cannot set font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), desired_font_face)
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
130
131
132
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
145
146
147
148 self.__set_GUI_size()
149
151 """Try to get previous window size from backend."""
152
153 cfg = gmCfg.cCfgSQL()
154
155
156 width = int(cfg.get2 (
157 option = 'main.window.width',
158 workplace = gmSurgery.gmCurrentPractice().active_workplace,
159 bias = 'workplace',
160 default = 800
161 ))
162
163
164 height = int(cfg.get2 (
165 option = 'main.window.height',
166 workplace = gmSurgery.gmCurrentPractice().active_workplace,
167 bias = 'workplace',
168 default = 600
169 ))
170
171 dw = wx.DisplaySize()[0]
172 dh = wx.DisplaySize()[1]
173
174 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
175 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
176 _log.debug('previous GUI size [%s:%s]', width, height)
177
178
179 if width > dw:
180 _log.debug('adjusting GUI width from %s to %s', width, dw)
181 width = dw
182
183 if height > dh:
184 _log.debug('adjusting GUI height from %s to %s', height, dh)
185 height = dh
186
187
188 if width < 100:
189 _log.debug('adjusting GUI width to minimum of 100 pixel')
190 width = 100
191 if height < 100:
192 _log.debug('adjusting GUI height to minimum of 100 pixel')
193 height = 100
194
195 _log.info('setting GUI to size [%s:%s]', width, height)
196
197 self.SetClientSize(wx.Size(width, height))
198
200 """Create the main menu entries.
201
202 Individual entries are farmed out to the modules.
203 """
204 global wx
205 self.mainmenu = wx.MenuBar()
206 self.__gb['main.mainmenu'] = self.mainmenu
207
208
209 menu_gnumed = wx.Menu()
210
211 self.menu_plugins = wx.Menu()
212 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
213
214 ID = wx.NewId()
215 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
216 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
217
218 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
219 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
220
221
222 menu_gnumed.AppendSeparator()
223
224
225 menu_config = wx.Menu()
226 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
227
228 item = menu_config.Append(-1, _('List configuration'), _('List all configuration items stored in the database.'))
229 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
230
231
232 menu_cfg_db = wx.Menu()
233 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
234
235 ID = wx.NewId()
236 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
237 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
238
239 ID = wx.NewId()
240 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
241 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
242
243
244 menu_cfg_client = wx.Menu()
245 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
246
247 ID = wx.NewId()
248 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
249 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
250
251 ID = wx.NewId()
252 menu_cfg_client.Append(ID, _('Temporary directory'), _('Configure the directory to use as scratch space for temporary files.'))
253 wx.EVT_MENU(self, ID, self.__on_configure_temp_dir)
254
255 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
256 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
257
258
259 menu_cfg_ui = wx.Menu()
260 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
261
262
263 menu_cfg_doc = wx.Menu()
264 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
265
266 ID = wx.NewId()
267 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
268 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
269
270 ID = wx.NewId()
271 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
272 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
273
274 ID = wx.NewId()
275 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
276 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
277
278
279 menu_cfg_update = wx.Menu()
280 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
281
282 ID = wx.NewId()
283 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
284 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
285
286 ID = wx.NewId()
287 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
288 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
289
290 ID = wx.NewId()
291 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
292 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
293
294
295 menu_cfg_pat_search = wx.Menu()
296 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
297
298 ID = wx.NewId()
299 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
300 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
301
302 ID = wx.NewId()
303 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
304 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
305
306 ID = wx.NewId()
307 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
308 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
309
310 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
311 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
312
313 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
314 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
315
316
317 menu_cfg_soap_editing = wx.Menu()
318 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
319
320 ID = wx.NewId()
321 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
322 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
323
324
325 menu_cfg_ext_tools = wx.Menu()
326 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
327
328
329
330
331
332 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
333 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
334
335 ID = wx.NewId()
336 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
337 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
338
339 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
340 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
341
342 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
343 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
344
345 item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.'))
346 self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item)
347
348 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
349 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
350
351 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
352 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
353
354 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
355 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
356
357 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
358 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
359
360
361 menu_cfg_emr = wx.Menu()
362 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
363
364 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
365 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
366
367 item = menu_cfg_emr.Append(-1, _('Primary doctor'), _('Select the primary doctor to fall back to for patients without one.'))
368 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
369
370
371 menu_cfg_encounter = wx.Menu()
372 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
373
374 ID = wx.NewId()
375 menu_cfg_encounter.Append(ID, _('Edit on patient change'), _('Edit encounter details on changing of patients.'))
376 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
377
378 ID = wx.NewId()
379 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
380 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
381
382 ID = wx.NewId()
383 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
384 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
385
386 ID = wx.NewId()
387 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
388 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
389
390 ID = wx.NewId()
391 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
392 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
393
394
395 menu_cfg_episode = wx.Menu()
396 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
397
398 ID = wx.NewId()
399 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
400 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
401
402
403 menu_master_data = wx.Menu()
404 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
405
406 item = menu_master_data.Append(-1, _('Workplace profiles'), _('Manage the plugins to load per workplace.'))
407 self.Bind(wx.EVT_MENU, self.__on_configure_workplace, item)
408
409 menu_master_data.AppendSeparator()
410
411 item = menu_master_data.Append(-1, _('&Document types'), _('Manage the document types available in the system.'))
412 self.Bind(wx.EVT_MENU, self.__on_edit_doc_types, item)
413
414 item = menu_master_data.Append(-1, _('&Form templates'), _('Manage templates for forms and letters.'))
415 self.Bind(wx.EVT_MENU, self.__on_manage_form_templates, item)
416
417 item = menu_master_data.Append(-1, _('&Text expansions'), _('Manage keyword based text expansion macros.'))
418 self.Bind(wx.EVT_MENU, self.__on_manage_text_expansion, item)
419
420 menu_master_data.AppendSeparator()
421
422 item = menu_master_data.Append(-1, _('&Encounter types'), _('Manage encounter types.'))
423 self.Bind(wx.EVT_MENU, self.__on_manage_encounter_types, item)
424
425 item = menu_master_data.Append(-1, _('&Provinces'), _('Manage provinces (counties, territories, ...).'))
426 self.Bind(wx.EVT_MENU, self.__on_manage_provinces, item)
427
428 menu_master_data.AppendSeparator()
429
430 item = menu_master_data.Append(-1, _('Substances'), _('Manage substances in use.'))
431 self.Bind(wx.EVT_MENU, self.__on_manage_substances, item)
432
433 item = menu_master_data.Append(-1, _('Drugs'), _('Manage branded drugs.'))
434 self.Bind(wx.EVT_MENU, self.__on_manage_branded_drugs, item)
435
436 item = menu_master_data.Append(-1, _('Drug components'), _('Manage components of branded drugs.'))
437 self.Bind(wx.EVT_MENU, self.__on_manage_substances_in_brands, item)
438
439 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
440 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
441
442 menu_master_data.AppendSeparator()
443
444 item = menu_master_data.Append(-1, _('Diagnostic orgs'), _('Manage diagnostic organisations (path labs etc).'))
445 self.Bind(wx.EVT_MENU, self.__on_manage_test_orgs, item)
446
447 item = menu_master_data.Append(-1, _('&Test types'), _('Manage test/measurement types.'))
448 self.Bind(wx.EVT_MENU, self.__on_manage_test_types, item)
449
450 item = menu_master_data.Append(-1, _('&Meta test types'), _('Show meta test/measurement types.'))
451 self.Bind(wx.EVT_MENU, self.__on_manage_meta_test_types, item)
452
453 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
454 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
455
456 menu_master_data.AppendSeparator()
457
458 item = menu_master_data.Append(-1, _('Vaccines'), _('Show known vaccines.'))
459 self.Bind(wx.EVT_MENU, self.__on_manage_vaccines, item)
460
461 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
462 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
463
464 item = menu_master_data.Append(-1, _('Immunizables'), _('Show conditions known to be preventable by vaccination.'))
465 self.Bind(wx.EVT_MENU, self.__on_manage_vaccination_indications, item)
466
467
468 menu_users = wx.Menu()
469 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
470
471 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
472 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
473
474 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
475 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
476
477
478 menu_gnumed.AppendSeparator()
479
480 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
481 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
482
483 self.mainmenu.Append(menu_gnumed, '&GNUmed')
484
485
486 menu_patient = wx.Menu()
487
488 ID_CREATE_PATIENT = wx.NewId()
489 menu_patient.Append(ID_CREATE_PATIENT, _('Register person'), _("Register a new person with GNUmed"))
490 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
491
492
493
494
495 ID_LOAD_EXT_PAT = wx.NewId()
496 menu_patient.Append(ID_LOAD_EXT_PAT, _('Load external'), _('Load and possibly create person from an external source.'))
497 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
498
499 ID_DEL_PAT = wx.NewId()
500 menu_patient.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
501 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
502
503 item = menu_patient.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
504 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
505
506 menu_patient.AppendSeparator()
507
508 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
509 menu_patient.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
510 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
511
512
513 ID = wx.NewId()
514 menu_patient.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
515 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
516
517 menu_patient.AppendSeparator()
518
519 self.mainmenu.Append(menu_patient, '&Person')
520 self.__gb['main.patientmenu'] = menu_patient
521
522
523 menu_emr = wx.Menu()
524 self.mainmenu.Append(menu_emr, _("&EMR"))
525 self.__gb['main.emrmenu'] = menu_emr
526
527
528 menu_emr_show = wx.Menu()
529 menu_emr.AppendMenu(wx.NewId(), _('Show as ...'), menu_emr_show)
530 self.__gb['main.emr_showmenu'] = menu_emr_show
531
532
533 item = menu_emr_show.Append(-1, _('Summary'), _('Show a high-level summary of the EMR.'))
534 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
535
536
537 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
538 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
539
540 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
541 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
542
543
544 menu_emr_edit = wx.Menu()
545 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
546
547 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'))
548 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
549
550 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
551 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
552
553 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
554 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
555
556 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
557 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
558
559 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
560 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
561
562 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
563 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
564
565 item = menu_emr_edit.Append(-1, _('&Measurement(s)'), _('Add (a) measurement result(s) for the current patient.'))
566 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
567
568 item = menu_emr_edit.Append(-1, _('&Vaccination(s)'), _('Add (a) vaccination(s) for the current patient.'))
569 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
570
571
572
573
574
575
576
577 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
578 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
579
580
581 item = menu_emr.Append(-1, _('&Encounters list'), _('List all encounters including empty ones.'))
582 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
583
584
585 menu_emr.AppendSeparator()
586
587 menu_emr_export = wx.Menu()
588 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
589
590 ID_EXPORT_EMR_ASCII = wx.NewId()
591 menu_emr_export.Append (
592 ID_EXPORT_EMR_ASCII,
593 _('Text document'),
594 _("Export the EMR of the active patient into a text file")
595 )
596 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
597
598 ID_EXPORT_EMR_JOURNAL = wx.NewId()
599 menu_emr_export.Append (
600 ID_EXPORT_EMR_JOURNAL,
601 _('Journal'),
602 _("Export the EMR of the active patient as a chronological journal into a text file")
603 )
604 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
605
606 ID_EXPORT_MEDISTAR = wx.NewId()
607 menu_emr_export.Append (
608 ID_EXPORT_MEDISTAR,
609 _('MEDISTAR import format'),
610 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
611 )
612 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
613
614
615 menu_emr.AppendSeparator()
616
617
618 menu_paperwork = wx.Menu()
619
620 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
621 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
622
623 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
624
625
626 self.menu_tools = wx.Menu()
627 self.__gb['main.toolsmenu'] = self.menu_tools
628 self.mainmenu.Append(self.menu_tools, _("&Tools"))
629
630 ID_DICOM_VIEWER = wx.NewId()
631 viewer = _('no viewer installed')
632 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
633 viewer = u'OsiriX'
634 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
635 viewer = u'Aeskulap'
636 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
637 viewer = u'AMIDE'
638 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
639 viewer = u'(x)medcon'
640 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)
641 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
642 if viewer == _('no viewer installed'):
643 _log.info('neither of OsiriX / Aeskulap / AMIDE / xmedcon found, disabling "DICOM viewer" menu item')
644 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
645
646
647
648
649
650 ID = wx.NewId()
651 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
652 wx.EVT_MENU(self, ID, self.__on_snellen)
653
654 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
655 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
656
657 self.menu_tools.AppendSeparator()
658
659
660 menu_knowledge = wx.Menu()
661 self.__gb['main.knowledgemenu'] = menu_knowledge
662 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
663
664 menu_drug_dbs = wx.Menu()
665 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
666
667 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
668 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
669
670
671
672
673
674
675 menu_id = wx.NewId()
676 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
677 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
678
679
680
681
682 ID_MEDICAL_LINKS = wx.NewId()
683 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
684 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
685
686
687 self.menu_office = wx.Menu()
688
689 self.__gb['main.officemenu'] = self.menu_office
690 self.mainmenu.Append(self.menu_office, _('&Office'))
691
692 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
693 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
694
695 self.menu_office.AppendSeparator()
696
697
698 help_menu = wx.Menu()
699
700 ID = wx.NewId()
701 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
702 wx.EVT_MENU(self, ID, self.__on_display_wiki)
703
704 ID = wx.NewId()
705 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
706 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
707
708 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
709 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
710
711 menu_debugging = wx.Menu()
712 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
713
714 ID_SCREENSHOT = wx.NewId()
715 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
716 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
717
718 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
719 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
720
721 ID = wx.NewId()
722 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
723 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
724
725 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
726 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
727
728 ID = wx.NewId()
729 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
730 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
731
732 ID_UNBLOCK = wx.NewId()
733 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
734 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
735
736 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
737 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
738
739
740
741
742 if _cfg.get(option = 'debug'):
743 ID_TOGGLE_PAT_LOCK = wx.NewId()
744 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient'), _('Lock/unlock patient - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
745 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
746
747 ID_TEST_EXCEPTION = wx.NewId()
748 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
749 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
750
751 ID = wx.NewId()
752 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
753 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
754 try:
755 import wx.lib.inspection
756 except ImportError:
757 menu_debugging.Enable(id = ID, enable = False)
758
759 help_menu.AppendSeparator()
760
761 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
762 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
763
764 ID_CONTRIBUTORS = wx.NewId()
765 help_menu.Append(ID_CONTRIBUTORS, _('GNUmed contributors'), _('show GNUmed contributors'))
766 wx.EVT_MENU(self, ID_CONTRIBUTORS, self.__on_show_contributors)
767
768 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
769 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
770
771 help_menu.AppendSeparator()
772
773
774 self.__gb['main.helpmenu'] = help_menu
775 self.mainmenu.Append(help_menu, _("&Help"))
776
777
778
779 self.SetMenuBar(self.mainmenu)
780
783
784
785
787 """register events we want to react to"""
788
789 wx.EVT_CLOSE(self, self.OnClose)
790 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
791 wx.EVT_END_SESSION(self, self._on_end_session)
792
793 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
794 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
795 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
796 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
797 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
798 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
799 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
800 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
801
802 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
803
804 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
805
806 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
807
808 _log.debug('registering plugin with menu system')
809 _log.debug(' generic name: %s', plugin_name)
810 _log.debug(' class name: %s', class_name)
811 _log.debug(' specific menu: %s', menu_name)
812 _log.debug(' menu item: %s', menu_item_name)
813
814
815 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
816 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
817 self.menu_id2plugin[item.Id] = class_name
818
819
820 if menu_name is not None:
821 menu = self.__gb['main.%smenu' % menu_name]
822 item = menu.Append(-1, menu_item_name, menu_help_string)
823 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
824 self.menu_id2plugin[item.Id] = class_name
825
826 return True
827
829 gmDispatcher.send (
830 signal = u'display_widget',
831 name = self.menu_id2plugin[evt.Id]
832 )
833
835 wx.Bell()
836 wx.Bell()
837 wx.Bell()
838 _log.warning('unhandled event detected: QUERY_END_SESSION')
839 _log.info('we should be saving ourselves from here')
840 gmLog2.flush()
841 print "unhandled event detected: QUERY_END_SESSION"
842
844 wx.Bell()
845 wx.Bell()
846 wx.Bell()
847 _log.warning('unhandled event detected: END_SESSION')
848 gmLog2.flush()
849 print "unhandled event detected: END_SESSION"
850
852 if not callable(callback):
853 raise TypeError(u'callback [%s] not callable' % callback)
854
855 self.__pre_exit_callbacks.append(callback)
856
857 - def _on_set_statustext_pubsub(self, context=None):
858 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
859 wx.CallAfter(self.SetStatusText, msg)
860
861 try:
862 if context.data['beep']:
863 wx.Bell()
864 except KeyError:
865 pass
866
867 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
868
869 if msg is None:
870 msg = _('programmer forgot to specify status message')
871
872 if loglevel is not None:
873 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
874
875 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
876 wx.CallAfter(self.SetStatusText, msg)
877
878 if beep:
879 wx.Bell()
880
882 wx.CallAfter(self.__on_db_maintenance_warning)
883
885
886 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
887 wx.Bell()
888 if not wx.GetApp().IsActive():
889 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
890
891 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
892
893 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
894 None,
895 -1,
896 caption = _('Database shutdown warning'),
897 question = _(
898 'The database will be shut down for maintenance\n'
899 'in a few minutes.\n'
900 '\n'
901 'In order to not suffer any loss of data you\n'
902 'will need to save your current work and log\n'
903 'out of this GNUmed client.\n'
904 ),
905 button_defs = [
906 {
907 u'label': _('Close now'),
908 u'tooltip': _('Close this GNUmed client immediately.'),
909 u'default': False
910 },
911 {
912 u'label': _('Finish work'),
913 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
914 u'default': True
915 }
916 ]
917 )
918 decision = dlg.ShowModal()
919 if decision == wx.ID_YES:
920 top_win = wx.GetApp().GetTopWindow()
921 wx.CallAfter(top_win.Close)
922
924 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
925
927
928 if not wx.GetApp().IsActive():
929 if urgent:
930 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
931 else:
932 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
933
934 if msg is not None:
935 self.SetStatusText(msg)
936
937 if urgent:
938 wx.Bell()
939
940 gmHooks.run_hook_script(hook = u'request_user_attention')
941
943 wx.CallAfter(self.__on_pat_name_changed)
944
946 self.__update_window_title()
947
949 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
950
952 self.__update_window_title()
953 try:
954 gmHooks.run_hook_script(hook = u'post_patient_activation')
955 except:
956 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
957 raise
958
960 return self.__sanity_check_encounter()
961
1019
1020
1021
1024
1032
1035
1036
1037
1052
1075
1077 from Gnumed.wxpython import gmAbout
1078 contribs = gmAbout.cContributorsDlg (
1079 parent = self,
1080 id = -1,
1081 title = _('GNUmed contributors'),
1082 size = wx.Size(400,600),
1083 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1084 )
1085 contribs.ShowModal()
1086 del contribs
1087 del gmAbout
1088
1089
1090
1092 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1093 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1094 self.Close(True)
1095 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1096
1099
1101 send = gmGuiHelpers.gm_show_question (
1102 _('This will send a notification about database downtime\n'
1103 'to all GNUmed clients connected to your database.\n'
1104 '\n'
1105 'Do you want to send the notification ?\n'
1106 ),
1107 _('Announcing database maintenance downtime')
1108 )
1109 if not send:
1110 return
1111 gmPG2.send_maintenance_notification()
1112
1113
1116
1117
1118
1150
1163
1164 gmCfgWidgets.configure_string_option (
1165 message = _(
1166 'Some network installations cannot cope with loading\n'
1167 'documents of arbitrary size in one piece from the\n'
1168 'database (mainly observed on older Windows versions)\n.'
1169 '\n'
1170 'Under such circumstances documents need to be retrieved\n'
1171 'in chunks and reassembled on the client.\n'
1172 '\n'
1173 'Here you can set the size (in Bytes) above which\n'
1174 'GNUmed will retrieve documents in chunks. Setting this\n'
1175 'value to 0 will disable the chunking protocol.'
1176 ),
1177 option = 'horstspace.blob_export_chunk_size',
1178 bias = 'workplace',
1179 default_value = 1024 * 1024,
1180 validator = is_valid
1181 )
1182
1183
1184
1252
1256
1257
1258
1267
1268 gmCfgWidgets.configure_string_option (
1269 message = _(
1270 'When GNUmed cannot find an OpenOffice server it\n'
1271 'will try to start one. OpenOffice, however, needs\n'
1272 'some time to fully start up.\n'
1273 '\n'
1274 'Here you can set the time for GNUmed to wait for OOo.\n'
1275 ),
1276 option = 'external.ooo.startup_settle_time',
1277 bias = 'workplace',
1278 default_value = 2.0,
1279 validator = is_valid
1280 )
1281
1284
1296
1297 gmCfgWidgets.configure_string_option (
1298 message = _(
1299 'GNUmed will use this URL to access a website which lets\n'
1300 'you report an adverse drug reaction (ADR).\n'
1301 '\n'
1302 'You can leave this empty but to set it to a specific\n'
1303 'address the URL must be accessible now.'
1304 ),
1305 option = 'external.urls.report_ADR',
1306 bias = 'user',
1307 default_value = u'https://dcgma.org/uaw/meldung.php',
1308 validator = is_valid
1309 )
1310
1322
1323 gmCfgWidgets.configure_string_option (
1324 message = _(
1325 'GNUmed will use this URL to access a website which lets\n'
1326 'you report an adverse vaccination reaction (vADR).\n'
1327 '\n'
1328 'If you set it to a specific address that URL must be\n'
1329 'accessible now. If you leave it empty it will fall back\n'
1330 'to the URL for reporting other adverse drug reactions.'
1331 ),
1332 option = 'external.urls.report_vaccine_ADR',
1333 bias = 'user',
1334 default_value = u'http://www.pei.de/cln_042/SharedDocs/Downloads/fachkreise/uaw/meldeboegen/b-ifsg-meldebogen,templateId=raw,property=publicationFile.pdf/b-ifsg-meldebogen.pdf',
1335 validator = is_valid
1336 )
1337
1349
1350 gmCfgWidgets.configure_string_option (
1351 message = _(
1352 'GNUmed will use this URL to access an encyclopedia of\n'
1353 'measurement/lab methods from within the measurments grid.\n'
1354 '\n'
1355 'You can leave this empty but to set it to a specific\n'
1356 'address the URL must be accessible now.'
1357 ),
1358 option = 'external.urls.measurements_encyclopedia',
1359 bias = 'user',
1360 default_value = u'http://www.laborlexikon.de',
1361 validator = is_valid
1362 )
1363
1375
1376 gmCfgWidgets.configure_string_option (
1377 message = _(
1378 'GNUmed will use this URL to access a page showing\n'
1379 'vaccination schedules.\n'
1380 '\n'
1381 'You can leave this empty but to set it to a specific\n'
1382 'address the URL must be accessible now.'
1383 ),
1384 option = 'external.urls.vaccination_plans',
1385 bias = 'user',
1386 default_value = u'http://www.bundesaerztekammer.de/downloads/ImpfempfehlungenRKI2009.pdf',
1387 validator = is_valid
1388 )
1389
1402
1403 gmCfgWidgets.configure_string_option (
1404 message = _(
1405 'Enter the shell command with which to start the\n'
1406 'the ACS risk assessment calculator.\n'
1407 '\n'
1408 'GNUmed will try to verify the path which may,\n'
1409 'however, fail if you are using an emulator such\n'
1410 'as Wine. Nevertheless, starting the calculator\n'
1411 'will work as long as the shell command is correct\n'
1412 'despite the failing test.'
1413 ),
1414 option = 'external.tools.acs_risk_calculator_cmd',
1415 bias = 'user',
1416 validator = is_valid
1417 )
1418
1421
1434
1435 gmCfgWidgets.configure_string_option (
1436 message = _(
1437 'Enter the shell command with which to start\n'
1438 'the FreeDiams drug database frontend.\n'
1439 '\n'
1440 'GNUmed will try to verify that path.'
1441 ),
1442 option = 'external.tools.freediams_cmd',
1443 bias = 'workplace',
1444 default_value = None,
1445 validator = is_valid
1446 )
1447
1460
1461 gmCfgWidgets.configure_string_option (
1462 message = _(
1463 'Enter the shell command with which to start the\n'
1464 'the IFAP drug database.\n'
1465 '\n'
1466 'GNUmed will try to verify the path which may,\n'
1467 'however, fail if you are using an emulator such\n'
1468 'as Wine. Nevertheless, starting IFAP will work\n'
1469 'as long as the shell command is correct despite\n'
1470 'the failing test.'
1471 ),
1472 option = 'external.ifap-win.shell_command',
1473 bias = 'workplace',
1474 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1475 validator = is_valid
1476 )
1477
1478
1479
1528
1529
1530
1547
1550
1553
1558
1559 gmCfgWidgets.configure_string_option (
1560 message = _(
1561 'When a patient is activated GNUmed checks the\n'
1562 "proximity of the patient's birthday.\n"
1563 '\n'
1564 'If the birthday falls within the range of\n'
1565 ' "today %s <the interval you set here>"\n'
1566 'GNUmed will remind you of the recent or\n'
1567 'imminent anniversary.'
1568 ) % u'\u2213',
1569 option = u'patient_search.dob_warn_interval',
1570 bias = 'user',
1571 default_value = '1 week',
1572 validator = is_valid
1573 )
1574
1576
1577 gmCfgWidgets.configure_boolean_option (
1578 parent = self,
1579 question = _(
1580 'When adding progress notes do you want to\n'
1581 'allow opening several unassociated, new\n'
1582 'episodes for a patient at once ?\n'
1583 '\n'
1584 'This can be particularly helpful when entering\n'
1585 'progress notes on entirely new patients presenting\n'
1586 'with a multitude of problems on their first visit.'
1587 ),
1588 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1589 button_tooltips = [
1590 _('Yes, allow for multiple new episodes concurrently.'),
1591 _('No, only allow editing one new episode at a time.')
1592 ]
1593 )
1594
1640
1641
1642
1645
1648
1662
1664 gmCfgWidgets.configure_boolean_option (
1665 parent = self,
1666 question = _(
1667 'Do you want GNUmed to show the encounter\n'
1668 'details editor when changing the active patient ?'
1669 ),
1670 option = 'encounter.show_editor_before_patient_change',
1671 button_tooltips = [
1672 _('Yes, show the encounter editor if it seems appropriate.'),
1673 _('No, never show the encounter editor even if it would seem useful.')
1674 ]
1675 )
1676
1681
1682 gmCfgWidgets.configure_string_option (
1683 message = _(
1684 'When a patient is activated GNUmed checks the\n'
1685 'chart for encounters lacking any entries.\n'
1686 '\n'
1687 'Any such encounters older than what you set\n'
1688 'here will be removed from the medical record.\n'
1689 '\n'
1690 'To effectively disable removal of such encounters\n'
1691 'set this option to an improbable value.\n'
1692 ),
1693 option = 'encounter.ttl_if_empty',
1694 bias = 'user',
1695 default_value = '1 week',
1696 validator = is_valid
1697 )
1698
1703
1704 gmCfgWidgets.configure_string_option (
1705 message = _(
1706 'When a patient is activated GNUmed checks the\n'
1707 'age of the most recent encounter.\n'
1708 '\n'
1709 'If that encounter is younger than this age\n'
1710 'the existing encounter will be continued.\n'
1711 '\n'
1712 '(If it is really old a new encounter is\n'
1713 ' started, or else GNUmed will ask you.)\n'
1714 ),
1715 option = 'encounter.minimum_ttl',
1716 bias = 'user',
1717 default_value = '1 hour 30 minutes',
1718 validator = is_valid
1719 )
1720
1725
1726 gmCfgWidgets.configure_string_option (
1727 message = _(
1728 'When a patient is activated GNUmed checks the\n'
1729 'age of the most recent encounter.\n'
1730 '\n'
1731 'If that encounter is older than this age\n'
1732 'GNUmed will always start a new encounter.\n'
1733 '\n'
1734 '(If it is very recent the existing encounter\n'
1735 ' is continued, or else GNUmed will ask you.)\n'
1736 ),
1737 option = 'encounter.maximum_ttl',
1738 bias = 'user',
1739 default_value = '6 hours',
1740 validator = is_valid
1741 )
1742
1751
1752 gmCfgWidgets.configure_string_option (
1753 message = _(
1754 'At any time there can only be one open (ongoing)\n'
1755 'episode for each health issue.\n'
1756 '\n'
1757 'When you try to open (add data to) an episode on a health\n'
1758 'issue GNUmed will check for an existing open episode on\n'
1759 'that issue. If there is any it will check the age of that\n'
1760 'episode. The episode is closed if it has been dormant (no\n'
1761 'data added, that is) for the period of time (in days) you\n'
1762 'set here.\n'
1763 '\n'
1764 "If the existing episode hasn't been dormant long enough\n"
1765 'GNUmed will consult you what to do.\n'
1766 '\n'
1767 'Enter maximum episode dormancy in DAYS:'
1768 ),
1769 option = 'episode.ttl',
1770 bias = 'user',
1771 default_value = 60,
1772 validator = is_valid
1773 )
1774
1805
1808
1823
1848
1860
1861 gmCfgWidgets.configure_string_option (
1862 message = _(
1863 'GNUmed can check for new releases being available. To do\n'
1864 'so it needs to load version information from an URL.\n'
1865 '\n'
1866 'The default URL is:\n'
1867 '\n'
1868 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1869 '\n'
1870 'but you can configure any other URL locally. Note\n'
1871 'that you must enter the location as a valid URL.\n'
1872 'Depending on the URL the client will need online\n'
1873 'access when checking for updates.'
1874 ),
1875 option = u'horstspace.update.url',
1876 bias = u'workplace',
1877 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1878 validator = is_valid
1879 )
1880
1898
1915
1926
1927 gmCfgWidgets.configure_string_option (
1928 message = _(
1929 'GNUmed can show the document review dialog after\n'
1930 'calling the appropriate viewer for that document.\n'
1931 '\n'
1932 'Select the conditions under which you want\n'
1933 'GNUmed to do so:\n'
1934 '\n'
1935 ' 0: never display the review dialog\n'
1936 ' 1: always display the dialog\n'
1937 ' 2: only if there is no previous review by me\n'
1938 '\n'
1939 'Note that if a viewer is configured to not block\n'
1940 'GNUmed during document display the review dialog\n'
1941 'will actually appear in parallel to the viewer.'
1942 ),
1943 option = u'horstspace.document_viewer.review_after_display',
1944 bias = u'user',
1945 default_value = 2,
1946 validator = is_valid
1947 )
1948
1962
1964
1965 dbcfg = gmCfg.cCfgSQL()
1966 cmd = dbcfg.get2 (
1967 option = u'external.tools.acs_risk_calculator_cmd',
1968 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1969 bias = 'user'
1970 )
1971
1972 if cmd is None:
1973 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
1974 return
1975
1976 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1977 try:
1978 subprocess.check_call (
1979 args = (cmd,),
1980 close_fds = True,
1981 cwd = cwd
1982 )
1983 except (OSError, ValueError, subprocess.CalledProcessError):
1984 _log.exception('there was a problem executing [%s]', cmd)
1985 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
1986 return
1987
1988 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
1989 for pdf in pdfs:
1990 try:
1991 open(pdf).close()
1992 except:
1993 _log.exception('error accessing [%s]', pdf)
1994 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the ARRIBA result in [%s] !') % pdf, beep = True)
1995 continue
1996
1997 doc = gmDocumentWidgets.save_file_as_new_document (
1998 parent = self,
1999 filename = pdf,
2000 document_type = u'risk assessment'
2001 )
2002
2003 try:
2004 os.remove(pdf)
2005 except StandardError:
2006 _log.exception('cannot remove [%s]', pdf)
2007
2008 if doc is None:
2009 continue
2010 doc['comment'] = u'ARRIBA: %s' % _('cardiovascular risk assessment')
2011 doc.save()
2012
2013 return
2014
2016 dlg = gmSnellen.cSnellenCfgDlg()
2017 if dlg.ShowModal() != wx.ID_OK:
2018 return
2019
2020 frame = gmSnellen.cSnellenChart (
2021 width = dlg.vals[0],
2022 height = dlg.vals[1],
2023 alpha = dlg.vals[2],
2024 mirr = dlg.vals[3],
2025 parent = None
2026 )
2027 frame.CentreOnScreen(wx.BOTH)
2028
2029
2030 frame.Show(True)
2031
2032
2034 webbrowser.open (
2035 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
2036 new = False,
2037 autoraise = True
2038 )
2039
2042
2044 webbrowser.open (
2045 url = 'http://www.kompendium.ch',
2046 new = False,
2047 autoraise = True
2048 )
2049
2050
2051
2055
2056
2057
2059 wx.CallAfter(self.__save_screenshot)
2060 evt.Skip()
2061
2063
2064 time.sleep(0.5)
2065
2066 rect = self.GetRect()
2067
2068
2069 if sys.platform == 'linux2':
2070 client_x, client_y = self.ClientToScreen((0, 0))
2071 border_width = client_x - rect.x
2072 title_bar_height = client_y - rect.y
2073
2074 if self.GetMenuBar():
2075 title_bar_height /= 2
2076 rect.width += (border_width * 2)
2077 rect.height += title_bar_height + border_width
2078
2079 wdc = wx.ScreenDC()
2080 mdc = wx.MemoryDC()
2081 img = wx.EmptyBitmap(rect.width, rect.height)
2082 mdc.SelectObject(img)
2083 mdc.Blit (
2084 0, 0,
2085 rect.width, rect.height,
2086 wdc,
2087 rect.x, rect.y
2088 )
2089
2090
2091 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2092 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2093 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2094
2096
2097 raise ValueError('raised ValueError to test exception handling')
2098
2100 import wx.lib.inspection
2101 wx.lib.inspection.InspectionTool().Show()
2102
2104 webbrowser.open (
2105 url = 'https://bugs.launchpad.net/gnumed/',
2106 new = False,
2107 autoraise = True
2108 )
2109
2111 webbrowser.open (
2112 url = 'http://wiki.gnumed.de',
2113 new = False,
2114 autoraise = True
2115 )
2116
2118 webbrowser.open (
2119 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
2120 new = False,
2121 autoraise = True
2122 )
2123
2125 webbrowser.open (
2126 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
2127 new = False,
2128 autoraise = True
2129 )
2130
2137
2141
2144
2151
2156
2158 name = os.path.basename(gmLog2._logfile_name)
2159 name, ext = os.path.splitext(name)
2160 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2161 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2162
2163 dlg = wx.FileDialog (
2164 parent = self,
2165 message = _("Save current log as..."),
2166 defaultDir = new_path,
2167 defaultFile = new_name,
2168 wildcard = "%s (*.log)|*.log" % _("log files"),
2169 style = wx.SAVE
2170 )
2171 choice = dlg.ShowModal()
2172 new_name = dlg.GetPath()
2173 dlg.Destroy()
2174 if choice != wx.ID_OK:
2175 return True
2176
2177 _log.warning('syncing log file for backup to [%s]', new_name)
2178 gmLog2.flush()
2179 shutil.copy2(gmLog2._logfile_name, new_name)
2180 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2181
2184
2185
2186
2188 """This is the wx.EVT_CLOSE handler.
2189
2190 - framework still functional
2191 """
2192 _log.debug('gmTopLevelFrame.OnClose() start')
2193 self._clean_exit()
2194 self.Destroy()
2195 _log.debug('gmTopLevelFrame.OnClose() end')
2196 return True
2197
2203
2208
2216
2223
2230
2240
2248
2256
2264
2272
2281
2289
2291 pat = gmPerson.gmCurrentPatient()
2292 if not pat.connected:
2293 gmDispatcher.send(signal = 'statustext', msg = _('Cannot show EMR summary. No active patient.'))
2294 return False
2295
2296 emr = pat.get_emr()
2297 dlg = wx.MessageDialog (
2298 parent = self,
2299 message = emr.format_statistics(),
2300 caption = _('EMR Summary'),
2301 style = wx.OK | wx.STAY_ON_TOP
2302 )
2303 dlg.ShowModal()
2304 dlg.Destroy()
2305 return True
2306
2309
2312
2314
2315 pat = gmPerson.gmCurrentPatient()
2316 if not pat.connected:
2317 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2318 return False
2319
2320 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2321
2322 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2323 gmTools.mkdir(aDefDir)
2324
2325 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2326 dlg = wx.FileDialog (
2327 parent = self,
2328 message = _("Save patient's EMR journal as..."),
2329 defaultDir = aDefDir,
2330 defaultFile = fname,
2331 wildcard = aWildcard,
2332 style = wx.SAVE
2333 )
2334 choice = dlg.ShowModal()
2335 fname = dlg.GetPath()
2336 dlg.Destroy()
2337 if choice != wx.ID_OK:
2338 return True
2339
2340 _log.debug('exporting EMR journal to [%s]' % fname)
2341
2342 exporter = gmPatientExporter.cEMRJournalExporter()
2343
2344 wx.BeginBusyCursor()
2345 try:
2346 fname = exporter.export_to_file(filename = fname)
2347 except:
2348 wx.EndBusyCursor()
2349 gmGuiHelpers.gm_show_error (
2350 _('Error exporting patient EMR as chronological journal.'),
2351 _('EMR journal export')
2352 )
2353 raise
2354 wx.EndBusyCursor()
2355
2356 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2357
2358 return True
2359
2366
2376
2378 curr_pat = gmPerson.gmCurrentPatient()
2379 if not curr_pat.connected:
2380 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2381 return False
2382
2383 enc = 'cp850'
2384 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2385 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2386 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2387
2390
2391
2392
2393
2394
2395
2396
2404
2412
2415
2422
2426
2430
2433
2436
2439
2442
2445
2448
2451
2454
2457
2460
2463
2466
2469
2474
2476 """Cleanup helper.
2477
2478 - should ALWAYS be called when this program is
2479 to be terminated
2480 - ANY code that should be executed before a
2481 regular shutdown should go in here
2482 - framework still functional
2483 """
2484 _log.debug('gmTopLevelFrame._clean_exit() start')
2485
2486
2487 listener = gmBackendListener.gmBackendListener()
2488 try:
2489 listener.shutdown()
2490 except:
2491 _log.exception('cannot stop backend notifications listener thread')
2492
2493
2494 if _scripting_listener is not None:
2495 try:
2496 _scripting_listener.shutdown()
2497 except:
2498 _log.exception('cannot stop scripting listener thread')
2499
2500
2501 self.clock_update_timer.Stop()
2502 gmTimer.shutdown()
2503 gmPhraseWheel.shutdown()
2504
2505
2506 for call_back in self.__pre_exit_callbacks:
2507 try:
2508 call_back()
2509 except:
2510 print "*** pre-exit callback failed ***"
2511 print call_back
2512 _log.exception('callback [%s] failed', call_back)
2513
2514
2515 gmDispatcher.send(u'application_closing')
2516
2517
2518 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2519
2520
2521 curr_width, curr_height = self.GetClientSizeTuple()
2522 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2523 dbcfg = gmCfg.cCfgSQL()
2524 dbcfg.set (
2525 option = 'main.window.width',
2526 value = curr_width,
2527 workplace = gmSurgery.gmCurrentPractice().active_workplace
2528 )
2529 dbcfg.set (
2530 option = 'main.window.height',
2531 value = curr_height,
2532 workplace = gmSurgery.gmCurrentPractice().active_workplace
2533 )
2534
2535 if _cfg.get(option = 'debug'):
2536 print '---=== GNUmed shutdown ===---'
2537 try:
2538 print _('You have to manually close this window to finalize shutting down GNUmed.')
2539 print _('This is so that you can inspect the console output at your leisure.')
2540 except UnicodeEncodeError:
2541 print 'You have to manually close this window to finalize shutting down GNUmed.'
2542 print 'This is so that you can inspect the console output at your leisure.'
2543 print '---=== GNUmed shutdown ===---'
2544
2545
2546 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2547
2548
2549 import threading
2550 _log.debug("%s active threads", threading.activeCount())
2551 for t in threading.enumerate():
2552 _log.debug('thread %s', t)
2553
2554 _log.debug('gmTopLevelFrame._clean_exit() end')
2555
2556
2557
2559
2560 if _cfg.get(option = 'slave'):
2561 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2562 _cfg.get(option = 'slave personality'),
2563 _cfg.get(option = 'xml-rpc port')
2564 )
2565 else:
2566 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2567
2569 """Update title of main window based on template.
2570
2571 This gives nice tooltips on iconified GNUmed instances.
2572
2573 User research indicates that in the title bar people want
2574 the date of birth, not the age, so please stick to this
2575 convention.
2576 """
2577 args = {}
2578
2579 pat = gmPerson.gmCurrentPatient()
2580 if pat.connected:
2581
2582
2583
2584
2585
2586
2587 args['pat'] = u'%s %s %s (%s) #%d' % (
2588 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2589
2590 pat['firstnames'],
2591 pat['lastnames'],
2592 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2593 pat['pk_identity']
2594 )
2595 else:
2596 args['pat'] = _('no patient')
2597
2598 args['prov'] = u'%s%s.%s' % (
2599 gmTools.coalesce(_provider['title'], u'', u'%s '),
2600 _provider['firstnames'][:1],
2601 _provider['lastnames']
2602 )
2603
2604 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2605
2606 self.SetTitle(self.__title_template % args)
2607
2608
2610 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2611 sb.SetStatusWidths([-1, 225])
2612
2613 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2614 self._cb_update_clock()
2615
2616 self.clock_update_timer.Start(milliseconds = 1000)
2617
2619 """Displays date and local time in the second slot of the status bar"""
2620 t = time.localtime(time.time())
2621 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2622 self.SetStatusText(st,1)
2623
2625 """Lock GNUmed client against unauthorized access"""
2626
2627
2628
2629 return
2630
2632 """Unlock the main notebook widgets
2633 As long as we are not logged into the database backend,
2634 all pages but the 'login' page of the main notebook widget
2635 are locked; i.e. not accessible by the user
2636 """
2637
2638
2639
2640
2641
2642 return
2643
2645 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2646
2648
2650
2651 self.__starting_up = True
2652
2653 gmExceptionHandlingWidgets.install_wx_exception_handler()
2654 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2655
2656
2657
2658
2659 self.SetAppName(u'gnumed')
2660 self.SetVendorName(u'The GNUmed Development Community.')
2661 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2662 paths.init_paths(wx = wx, app_name = u'gnumed')
2663
2664 if not self.__setup_prefs_file():
2665 return False
2666
2667 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2668
2669 self.__guibroker = gmGuiBroker.GuiBroker()
2670 self.__setup_platform()
2671
2672 if not self.__establish_backend_connection():
2673 return False
2674
2675 if not _cfg.get(option = 'skip-update-check'):
2676 self.__check_for_updates()
2677
2678 if _cfg.get(option = 'slave'):
2679 if not self.__setup_scripting_listener():
2680 return False
2681
2682
2683 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640,440))
2684 frame.CentreOnScreen(wx.BOTH)
2685 self.SetTopWindow(frame)
2686 frame.Show(True)
2687
2688 if _cfg.get(option = 'debug'):
2689 self.RedirectStdio()
2690 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2691
2692
2693 print '---=== GNUmed startup ===---'
2694 print _('redirecting STDOUT/STDERR to this log window')
2695 print '---=== GNUmed startup ===---'
2696
2697 self.__setup_user_activity_timer()
2698 self.__register_events()
2699
2700 wx.CallAfter(self._do_after_init)
2701
2702 return True
2703
2705 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2706
2707 - after destroying all application windows and controls
2708 - before wx.Windows internal cleanup
2709 """
2710 _log.debug('gmApp.OnExit() start')
2711
2712 self.__shutdown_user_activity_timer()
2713
2714 if _cfg.get(option = 'debug'):
2715 self.RestoreStdio()
2716 sys.stdin = sys.__stdin__
2717 sys.stdout = sys.__stdout__
2718 sys.stderr = sys.__stderr__
2719
2720 _log.debug('gmApp.OnExit() end')
2721
2723 wx.Bell()
2724 wx.Bell()
2725 wx.Bell()
2726 _log.warning('unhandled event detected: QUERY_END_SESSION')
2727 _log.info('we should be saving ourselves from here')
2728 gmLog2.flush()
2729 print "unhandled event detected: QUERY_END_SESSION"
2730
2732 wx.Bell()
2733 wx.Bell()
2734 wx.Bell()
2735 _log.warning('unhandled event detected: END_SESSION')
2736 gmLog2.flush()
2737 print "unhandled event detected: END_SESSION"
2738
2749
2751 self.user_activity_detected = True
2752 evt.Skip()
2753
2755
2756 if self.user_activity_detected:
2757 self.elapsed_inactivity_slices = 0
2758 self.user_activity_detected = False
2759 self.elapsed_inactivity_slices += 1
2760 else:
2761 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2762
2763 pass
2764
2765 self.user_activity_timer.Start(oneShot = True)
2766
2767
2768
2770 try:
2771 kwargs['originated_in_database']
2772 print '==> got notification from database "%s":' % kwargs['signal']
2773 except KeyError:
2774 print '==> received signal from client: "%s"' % kwargs['signal']
2775
2776 del kwargs['signal']
2777 for key in kwargs.keys():
2778 print ' [%s]: %s' % (key, kwargs[key])
2779
2781 print "wx.lib.pubsub message:"
2782 print msg.topic
2783 print msg.data
2784
2790
2792 self.user_activity_detected = True
2793 self.elapsed_inactivity_slices = 0
2794
2795 self.max_user_inactivity_slices = 15
2796 self.user_activity_timer = gmTimer.cTimer (
2797 callback = self._on_user_activity_timer_expired,
2798 delay = 2000
2799 )
2800 self.user_activity_timer.Start(oneShot=True)
2801
2803 try:
2804 self.user_activity_timer.Stop()
2805 del self.user_activity_timer
2806 except:
2807 pass
2808
2810 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2811 wx.EVT_END_SESSION(self, self._on_end_session)
2812
2813
2814
2815
2816
2817 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2818
2819 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2820 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2846
2848 """Handle all the database related tasks necessary for startup."""
2849
2850
2851 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
2852
2853 from Gnumed.wxpython import gmAuthWidgets
2854 connected = gmAuthWidgets.connect_to_database (
2855 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
2856 require_version = not override
2857 )
2858 if not connected:
2859 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
2860 return False
2861
2862
2863 try:
2864 global _provider
2865 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
2866 except ValueError:
2867 account = gmPG2.get_current_user()
2868 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
2869 msg = _(
2870 'The database account [%s] cannot be used as a\n'
2871 'staff member login for GNUmed. There was an\n'
2872 'error retrieving staff details for it.\n\n'
2873 'Please ask your administrator for help.\n'
2874 ) % account
2875 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
2876 return False
2877
2878
2879 tmp = '%s%s %s (%s = %s)' % (
2880 gmTools.coalesce(_provider['title'], ''),
2881 _provider['firstnames'],
2882 _provider['lastnames'],
2883 _provider['short_alias'],
2884 _provider['db_user']
2885 )
2886 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
2887
2888
2889 surgery = gmSurgery.gmCurrentPractice()
2890 msg = surgery.db_logon_banner
2891 if msg.strip() != u'':
2892
2893 login = gmPG2.get_default_login()
2894 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
2895 login.database,
2896 gmTools.coalesce(login.host, u'localhost')
2897 ))
2898 msg = auth + msg + u'\n\n'
2899
2900 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
2901 None,
2902 -1,
2903 caption = _('Verifying database'),
2904 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
2905 button_defs = [
2906 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
2907 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
2908 ]
2909 )
2910 go_on = dlg.ShowModal()
2911 dlg.Destroy()
2912 if go_on != wx.ID_YES:
2913 _log.info('user decided to not connect to this database')
2914 return False
2915
2916
2917 self.__check_db_lang()
2918
2919 return True
2920
2922 """Setup access to a config file for storing preferences."""
2923
2924 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2925
2926 candidates = []
2927 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
2928 if explicit_file is not None:
2929 candidates.append(explicit_file)
2930
2931 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
2932 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
2933 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
2934
2935 prefs_file = None
2936 for candidate in candidates:
2937 try:
2938 open(candidate, 'a+').close()
2939 prefs_file = candidate
2940 break
2941 except IOError:
2942 continue
2943
2944 if prefs_file is None:
2945 msg = _(
2946 'Cannot find configuration file in any of:\n'
2947 '\n'
2948 ' %s\n'
2949 'You may need to use the comand line option\n'
2950 '\n'
2951 ' --conf-file=<FILE>'
2952 ) % '\n '.join(candidates)
2953 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
2954 return False
2955
2956 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
2957 _log.info('user preferences file: %s', prefs_file)
2958
2959 return True
2960
2962
2963 from socket import error as SocketError
2964 from Gnumed.pycommon import gmScriptingListener
2965 from Gnumed.wxpython import gmMacro
2966
2967 slave_personality = gmTools.coalesce (
2968 _cfg.get (
2969 group = u'workplace',
2970 option = u'slave personality',
2971 source_order = [
2972 ('explicit', 'return'),
2973 ('workbase', 'return'),
2974 ('user', 'return'),
2975 ('system', 'return')
2976 ]
2977 ),
2978 u'gnumed-client'
2979 )
2980 _cfg.set_option(option = 'slave personality', value = slave_personality)
2981
2982
2983 port = int (
2984 gmTools.coalesce (
2985 _cfg.get (
2986 group = u'workplace',
2987 option = u'xml-rpc port',
2988 source_order = [
2989 ('explicit', 'return'),
2990 ('workbase', 'return'),
2991 ('user', 'return'),
2992 ('system', 'return')
2993 ]
2994 ),
2995 9999
2996 )
2997 )
2998 _cfg.set_option(option = 'xml-rpc port', value = port)
2999
3000 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3001 global _scripting_listener
3002 try:
3003 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3004 except SocketError, e:
3005 _log.exception('cannot start GNUmed XML-RPC server')
3006 gmGuiHelpers.gm_show_error (
3007 aMessage = (
3008 'Cannot start the GNUmed server:\n'
3009 '\n'
3010 ' [%s]'
3011 ) % e,
3012 aTitle = _('GNUmed startup')
3013 )
3014 return False
3015
3016 return True
3017
3038
3040 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3041 _log.warning("system locale is undefined (probably meaning 'C')")
3042 return True
3043
3044
3045 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3046 db_lang = rows[0]['lang']
3047
3048 if db_lang is None:
3049 _log.debug("database locale currently not set")
3050 msg = _(
3051 "There is no language selected in the database for user [%s].\n"
3052 "Your system language is currently set to [%s].\n\n"
3053 "Do you want to set the database language to '%s' ?\n\n"
3054 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3055 checkbox_msg = _('Remember to ignore missing language')
3056 else:
3057 _log.debug("current database locale: [%s]" % db_lang)
3058 msg = _(
3059 "The currently selected database language ('%s') does\n"
3060 "not match the current system language ('%s').\n"
3061 "\n"
3062 "Do you want to set the database language to '%s' ?\n"
3063 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3064 checkbox_msg = _('Remember to ignore language mismatch')
3065
3066
3067 if db_lang == gmI18N.system_locale_level['full']:
3068 _log.debug('Database locale (%s) up to date.' % db_lang)
3069 return True
3070 if db_lang == gmI18N.system_locale_level['country']:
3071 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3072 return True
3073 if db_lang == gmI18N.system_locale_level['language']:
3074 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3075 return True
3076
3077 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3078
3079
3080 ignored_sys_lang = _cfg.get (
3081 group = u'backend',
3082 option = u'ignored mismatching system locale',
3083 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3084 )
3085
3086
3087 if gmI18N.system_locale == ignored_sys_lang:
3088 _log.info('configured to ignore system-to-database locale mismatch')
3089 return True
3090
3091
3092 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3093 None,
3094 -1,
3095 caption = _('Checking database language settings'),
3096 question = msg,
3097 button_defs = [
3098 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3099 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3100 ],
3101 show_checkbox = True,
3102 checkbox_msg = checkbox_msg,
3103 checkbox_tooltip = _(
3104 'Checking this will make GNUmed remember your decision\n'
3105 'until the system language is changed.\n'
3106 '\n'
3107 'You can also reactivate this inquiry by removing the\n'
3108 'corresponding "ignore" option from the configuration file\n'
3109 '\n'
3110 ' [%s]'
3111 ) % _cfg.get(option = 'user_preferences_file')
3112 )
3113 decision = dlg.ShowModal()
3114 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3115 dlg.Destroy()
3116
3117 if decision == wx.ID_NO:
3118 if not remember_ignoring_problem:
3119 return True
3120 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3121 gmCfg2.set_option_in_INI_file (
3122 filename = _cfg.get(option = 'user_preferences_file'),
3123 group = 'backend',
3124 option = 'ignored mismatching system locale',
3125 value = gmI18N.system_locale
3126 )
3127 return True
3128
3129
3130 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3131 if len(lang) > 0:
3132
3133
3134 rows, idx = gmPG2.run_rw_queries (
3135 link_obj = None,
3136 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3137 return_data = True
3138 )
3139 if rows[0][0]:
3140 _log.debug("Successfully set database language to [%s]." % lang)
3141 else:
3142 _log.error('Cannot set database language to [%s].' % lang)
3143 continue
3144 return True
3145
3146
3147 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3148 gmPG2.run_rw_queries(queries = [{
3149 'cmd': u'select i18n.force_curr_lang(%s)',
3150 'args': [gmI18N.system_locale_level['country']]
3151 }])
3152
3153 return True
3154
3156 try:
3157 kwargs['originated_in_database']
3158 print '==> got notification from database "%s":' % kwargs['signal']
3159 except KeyError:
3160 print '==> received signal from client: "%s"' % kwargs['signal']
3161
3162 del kwargs['signal']
3163 for key in kwargs.keys():
3164
3165 try: print ' [%s]: %s' % (key, kwargs[key])
3166 except: print 'cannot print signal information'
3167
3169
3170 try:
3171 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3172 print ' data: %s' % msg.data
3173 print msg
3174 except: print 'problem printing pubsub message information'
3175
3177
3178 if _cfg.get(option = 'debug'):
3179 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3180 _log.debug('gmDispatcher signal monitor activated')
3181 wx.lib.pubsub.Publisher().subscribe (
3182 listener = _signal_debugging_monitor_pubsub,
3183 topic = wx.lib.pubsub.getStrAllTopics()
3184 )
3185 _log.debug('wx.lib.pubsub signal monitor activated')
3186
3187
3188
3189
3190 app = gmApp(redirect = False, clearSigInt = False)
3191 app.MainLoop()
3192
3193
3194
3195 if __name__ == '__main__':
3196
3197 from GNUmed.pycommon import gmI18N
3198 gmI18N.activate_locale()
3199 gmI18N.install_domain()
3200
3201 _log.info('Starting up as main module.')
3202 main()
3203
3204
3205