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 from Gnumed.wxpython import gmI18nWidgets, gmCodingWidgets
71
72
73 try:
74 _('dummy-no-need-to-translate-but-make-epydoc-happy')
75 except NameError:
76 _ = lambda x:x
77
78 _cfg = gmCfg2.gmCfgData()
79 _provider = None
80 _scripting_listener = None
81
82 _log = logging.getLogger('gm.main')
83 _log.info(__version__)
84 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
85
86
88 """GNUmed client's main windows frame.
89
90 This is where it all happens. Avoid popping up any other windows.
91 Most user interaction should happen to and from widgets within this frame
92 """
93
94 - def __init__(self, parent, id, title, size=wx.DefaultSize):
95 """You'll have to browse the source to understand what the constructor does
96 """
97 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
98
99 if wx.Platform == '__WXMSW__':
100 font = self.GetFont()
101 _log.debug('default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
102 desired_font_face = u'DejaVu Sans'
103 success = font.SetFaceName(desired_font_face)
104 if success:
105 self.SetFont(font)
106 _log.debug('setting font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
107 else:
108 font = self.GetFont()
109 _log.error('cannot set font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), desired_font_face)
110
111 self.__gb = gmGuiBroker.GuiBroker()
112 self.__pre_exit_callbacks = []
113 self.bar_width = -1
114 self.menu_id2plugin = {}
115
116 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
117
118 self.__setup_main_menu()
119 self.setup_statusbar()
120 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
121 gmTools.coalesce(_provider['title'], ''),
122 _provider['firstnames'][:1],
123 _provider['lastnames'],
124 _provider['short_alias'],
125 _provider['db_user']
126 ))
127
128 self.__set_window_title_template()
129 self.__update_window_title()
130
131
132
133
134
135 self.SetIcon(gmTools.get_icon(wx = wx))
136
137 self.__register_events()
138
139 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
140 self.vbox = wx.BoxSizer(wx.VERTICAL)
141 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
142
143 self.SetAutoLayout(True)
144 self.SetSizerAndFit(self.vbox)
145
146
147
148
149
150 self.__set_GUI_size()
151
153 """Try to get previous window size from backend."""
154
155 cfg = gmCfg.cCfgSQL()
156
157
158 width = int(cfg.get2 (
159 option = 'main.window.width',
160 workplace = gmSurgery.gmCurrentPractice().active_workplace,
161 bias = 'workplace',
162 default = 800
163 ))
164
165
166 height = int(cfg.get2 (
167 option = 'main.window.height',
168 workplace = gmSurgery.gmCurrentPractice().active_workplace,
169 bias = 'workplace',
170 default = 600
171 ))
172
173 dw = wx.DisplaySize()[0]
174 dh = wx.DisplaySize()[1]
175
176 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
177 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
178 _log.debug('previous GUI size [%s:%s]', width, height)
179
180
181 if width > dw:
182 _log.debug('adjusting GUI width from %s to %s', width, dw)
183 width = dw
184
185 if height > dh:
186 _log.debug('adjusting GUI height from %s to %s', height, dh)
187 height = dh
188
189
190 if width < 100:
191 _log.debug('adjusting GUI width to minimum of 100 pixel')
192 width = 100
193 if height < 100:
194 _log.debug('adjusting GUI height to minimum of 100 pixel')
195 height = 100
196
197 _log.info('setting GUI to size [%s:%s]', width, height)
198
199 self.SetClientSize(wx.Size(width, height))
200
202 """Create the main menu entries.
203
204 Individual entries are farmed out to the modules.
205 """
206 global wx
207 self.mainmenu = wx.MenuBar()
208 self.__gb['main.mainmenu'] = self.mainmenu
209
210
211 menu_gnumed = wx.Menu()
212
213 self.menu_plugins = wx.Menu()
214 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
215
216 ID = wx.NewId()
217 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
218 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
219
220 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
221 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
222
223
224 menu_gnumed.AppendSeparator()
225
226
227 menu_config = wx.Menu()
228 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
229
230 item = menu_config.Append(-1, _('List configuration'), _('List all configuration items stored in the database.'))
231 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
232
233
234 menu_cfg_db = wx.Menu()
235 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
236
237 ID = wx.NewId()
238 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
239 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
240
241 ID = wx.NewId()
242 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
243 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
244
245
246 menu_cfg_client = wx.Menu()
247 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
248
249 ID = wx.NewId()
250 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
251 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
252
253 ID = wx.NewId()
254 menu_cfg_client.Append(ID, _('Temporary directory'), _('Configure the directory to use as scratch space for temporary files.'))
255 wx.EVT_MENU(self, ID, self.__on_configure_temp_dir)
256
257 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
258 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
259
260
261 menu_cfg_ui = wx.Menu()
262 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
263
264
265 menu_cfg_doc = wx.Menu()
266 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
267
268 ID = wx.NewId()
269 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
270 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
271
272 ID = wx.NewId()
273 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
274 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
275
276 ID = wx.NewId()
277 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
278 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
279
280
281 menu_cfg_update = wx.Menu()
282 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
283
284 ID = wx.NewId()
285 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
286 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
287
288 ID = wx.NewId()
289 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
290 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
291
292 ID = wx.NewId()
293 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
294 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
295
296
297 menu_cfg_pat_search = wx.Menu()
298 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
299
300 ID = wx.NewId()
301 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
302 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
303
304 ID = wx.NewId()
305 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
306 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
307
308 ID = wx.NewId()
309 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
310 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
311
312 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
313 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
314
315 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
316 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
317
318
319 menu_cfg_soap_editing = wx.Menu()
320 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
321
322 ID = wx.NewId()
323 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
324 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
325
326
327 menu_cfg_ext_tools = wx.Menu()
328 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
329
330
331
332
333
334 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
335 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
336
337 ID = wx.NewId()
338 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
339 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
340
341 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
342 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
343
344 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
345 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
346
347 item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.'))
348 self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item)
349
350 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
351 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
352
353 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
354 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
355
356 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
357 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
358
359 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
360 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
361
362
363 menu_cfg_emr = wx.Menu()
364 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
365
366 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
367 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
368
369 item = menu_cfg_emr.Append(-1, _('Primary doctor'), _('Select the primary doctor to fall back to for patients without one.'))
370 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
371
372
373 menu_cfg_encounter = wx.Menu()
374 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
375
376 ID = wx.NewId()
377 menu_cfg_encounter.Append(ID, _('Edit on patient change'), _('Edit encounter details on changing of patients.'))
378 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
379
380 ID = wx.NewId()
381 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
382 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
383
384 ID = wx.NewId()
385 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
386 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
387
388 ID = wx.NewId()
389 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
390 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
391
392 ID = wx.NewId()
393 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
394 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
395
396
397 menu_cfg_episode = wx.Menu()
398 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
399
400 ID = wx.NewId()
401 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
402 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
403
404
405 menu_master_data = wx.Menu()
406 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
407
408 item = menu_master_data.Append(-1, _('Workplace profiles'), _('Manage the plugins to load per workplace.'))
409 self.Bind(wx.EVT_MENU, self.__on_configure_workplace, item)
410
411 menu_master_data.AppendSeparator()
412
413 item = menu_master_data.Append(-1, _('&Document types'), _('Manage the document types available in the system.'))
414 self.Bind(wx.EVT_MENU, self.__on_edit_doc_types, item)
415
416 item = menu_master_data.Append(-1, _('&Form templates'), _('Manage templates for forms and letters.'))
417 self.Bind(wx.EVT_MENU, self.__on_manage_form_templates, item)
418
419 item = menu_master_data.Append(-1, _('&Text expansions'), _('Manage keyword based text expansion macros.'))
420 self.Bind(wx.EVT_MENU, self.__on_manage_text_expansion, item)
421
422 item = menu_master_data.Append(-1, _('&Translations (DB)'), _('Manage string translations in the database.'))
423 self.Bind(wx.EVT_MENU, self.__on_manage_translations, item)
424
425 item = menu_master_data.Append(-1, _('Codes'), _('Browse codes with coded terms.'))
426 self.Bind(wx.EVT_MENU, self.__on_browse_coded_terms, item)
427
428 menu_master_data.AppendSeparator()
429
430 item = menu_master_data.Append(-1, _('&Encounter types'), _('Manage encounter types.'))
431 self.Bind(wx.EVT_MENU, self.__on_manage_encounter_types, item)
432
433 item = menu_master_data.Append(-1, _('&Provinces'), _('Manage provinces (counties, territories, ...).'))
434 self.Bind(wx.EVT_MENU, self.__on_manage_provinces, item)
435
436 menu_master_data.AppendSeparator()
437
438 item = menu_master_data.Append(-1, _('Substances'), _('Manage substances in use.'))
439 self.Bind(wx.EVT_MENU, self.__on_manage_substances, item)
440
441 item = menu_master_data.Append(-1, _('Drugs'), _('Manage branded drugs.'))
442 self.Bind(wx.EVT_MENU, self.__on_manage_branded_drugs, item)
443
444 item = menu_master_data.Append(-1, _('Drug components'), _('Manage components of branded drugs.'))
445 self.Bind(wx.EVT_MENU, self.__on_manage_substances_in_brands, item)
446
447 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
448 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
449
450 menu_master_data.AppendSeparator()
451
452 item = menu_master_data.Append(-1, _('Diagnostic orgs'), _('Manage diagnostic organisations (path labs etc).'))
453 self.Bind(wx.EVT_MENU, self.__on_manage_test_orgs, item)
454
455 item = menu_master_data.Append(-1, _('&Test types'), _('Manage test/measurement types.'))
456 self.Bind(wx.EVT_MENU, self.__on_manage_test_types, item)
457
458 item = menu_master_data.Append(-1, _('&Meta test types'), _('Show meta test/measurement types.'))
459 self.Bind(wx.EVT_MENU, self.__on_manage_meta_test_types, item)
460
461 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
462 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
463
464 menu_master_data.AppendSeparator()
465
466 item = menu_master_data.Append(-1, _('Vaccines'), _('Show known vaccines.'))
467 self.Bind(wx.EVT_MENU, self.__on_manage_vaccines, item)
468
469 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
470 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
471
472 item = menu_master_data.Append(-1, _('Immunizables'), _('Show conditions known to be preventable by vaccination.'))
473 self.Bind(wx.EVT_MENU, self.__on_manage_vaccination_indications, item)
474
475
476 menu_users = wx.Menu()
477 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
478
479 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
480 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
481
482 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
483 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
484
485
486 menu_gnumed.AppendSeparator()
487
488 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
489 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
490
491 self.mainmenu.Append(menu_gnumed, '&GNUmed')
492
493
494 menu_patient = wx.Menu()
495
496 ID_CREATE_PATIENT = wx.NewId()
497 menu_patient.Append(ID_CREATE_PATIENT, _('Register person'), _("Register a new person with GNUmed"))
498 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
499
500
501
502
503 ID_LOAD_EXT_PAT = wx.NewId()
504 menu_patient.Append(ID_LOAD_EXT_PAT, _('Load external'), _('Load and possibly create person from an external source.'))
505 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
506
507 ID_DEL_PAT = wx.NewId()
508 menu_patient.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
509 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
510
511 item = menu_patient.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
512 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
513
514 menu_patient.AppendSeparator()
515
516 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
517 menu_patient.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
518 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
519
520
521 ID = wx.NewId()
522 menu_patient.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
523 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
524
525 menu_patient.AppendSeparator()
526
527 self.mainmenu.Append(menu_patient, '&Person')
528 self.__gb['main.patientmenu'] = menu_patient
529
530
531 menu_emr = wx.Menu()
532 self.mainmenu.Append(menu_emr, _("&EMR"))
533 self.__gb['main.emrmenu'] = menu_emr
534
535
536 menu_emr_show = wx.Menu()
537 menu_emr.AppendMenu(wx.NewId(), _('Show as ...'), menu_emr_show)
538 self.__gb['main.emr_showmenu'] = menu_emr_show
539
540
541 item = menu_emr_show.Append(-1, _('Summary'), _('Show a high-level summary of the EMR.'))
542 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
543
544
545 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
546 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
547
548 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
549 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
550
551
552 menu_emr_edit = wx.Menu()
553 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
554
555 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'))
556 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
557
558 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
559 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
560
561 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
562 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
563
564 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
565 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
566
567 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
568 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
569
570 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
571 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
572
573 item = menu_emr_edit.Append(-1, _('&Measurement(s)'), _('Add (a) measurement result(s) for the current patient.'))
574 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
575
576 item = menu_emr_edit.Append(-1, _('&Vaccination(s)'), _('Add (a) vaccination(s) for the current patient.'))
577 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
578
579
580
581
582
583
584
585 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
586 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
587
588
589 item = menu_emr.Append(-1, _('&Encounters list'), _('List all encounters including empty ones.'))
590 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
591
592
593 menu_emr.AppendSeparator()
594
595 menu_emr_export = wx.Menu()
596 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
597
598 ID_EXPORT_EMR_ASCII = wx.NewId()
599 menu_emr_export.Append (
600 ID_EXPORT_EMR_ASCII,
601 _('Text document'),
602 _("Export the EMR of the active patient into a text file")
603 )
604 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
605
606 ID_EXPORT_EMR_JOURNAL = wx.NewId()
607 menu_emr_export.Append (
608 ID_EXPORT_EMR_JOURNAL,
609 _('Journal'),
610 _("Export the EMR of the active patient as a chronological journal into a text file")
611 )
612 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
613
614 ID_EXPORT_MEDISTAR = wx.NewId()
615 menu_emr_export.Append (
616 ID_EXPORT_MEDISTAR,
617 _('MEDISTAR import format'),
618 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
619 )
620 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
621
622
623 menu_emr.AppendSeparator()
624
625
626 menu_paperwork = wx.Menu()
627
628 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
629 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
630
631 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
632
633
634 self.menu_tools = wx.Menu()
635 self.__gb['main.toolsmenu'] = self.menu_tools
636 self.mainmenu.Append(self.menu_tools, _("&Tools"))
637
638 ID_DICOM_VIEWER = wx.NewId()
639 viewer = _('no viewer installed')
640 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
641 viewer = u'OsiriX'
642 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
643 viewer = u'Aeskulap'
644 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
645 viewer = u'AMIDE'
646 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
647 viewer = u'DicomScope'
648 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
649 viewer = u'(x)medcon'
650 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)
651 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
652 if viewer == _('no viewer installed'):
653 _log.info('neither of OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
654 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
655
656
657
658
659
660 ID = wx.NewId()
661 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
662 wx.EVT_MENU(self, ID, self.__on_snellen)
663
664 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
665 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
666
667 self.menu_tools.AppendSeparator()
668
669
670 menu_knowledge = wx.Menu()
671 self.__gb['main.knowledgemenu'] = menu_knowledge
672 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
673
674 menu_drug_dbs = wx.Menu()
675 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
676
677 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
678 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
679
680
681
682
683
684
685 menu_id = wx.NewId()
686 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
687 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
688
689
690
691
692 ID_MEDICAL_LINKS = wx.NewId()
693 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
694 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
695
696
697 self.menu_office = wx.Menu()
698
699 self.__gb['main.officemenu'] = self.menu_office
700 self.mainmenu.Append(self.menu_office, _('&Office'))
701
702 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
703 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
704
705 self.menu_office.AppendSeparator()
706
707
708 help_menu = wx.Menu()
709
710 ID = wx.NewId()
711 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
712 wx.EVT_MENU(self, ID, self.__on_display_wiki)
713
714 ID = wx.NewId()
715 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
716 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
717
718 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
719 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
720
721 menu_debugging = wx.Menu()
722 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
723
724 ID_SCREENSHOT = wx.NewId()
725 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
726 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
727
728 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
729 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
730
731 ID = wx.NewId()
732 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
733 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
734
735 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
736 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
737
738 ID = wx.NewId()
739 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
740 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
741
742 ID_UNBLOCK = wx.NewId()
743 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
744 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
745
746 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
747 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
748
749
750
751
752 if _cfg.get(option = 'debug'):
753 ID_TOGGLE_PAT_LOCK = wx.NewId()
754 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient'), _('Lock/unlock patient - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
755 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
756
757 ID_TEST_EXCEPTION = wx.NewId()
758 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
759 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
760
761 ID = wx.NewId()
762 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
763 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
764 try:
765 import wx.lib.inspection
766 except ImportError:
767 menu_debugging.Enable(id = ID, enable = False)
768
769 help_menu.AppendSeparator()
770
771 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
772 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
773
774 ID_CONTRIBUTORS = wx.NewId()
775 help_menu.Append(ID_CONTRIBUTORS, _('GNUmed contributors'), _('show GNUmed contributors'))
776 wx.EVT_MENU(self, ID_CONTRIBUTORS, self.__on_show_contributors)
777
778 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
779 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
780
781 help_menu.AppendSeparator()
782
783
784 self.__gb['main.helpmenu'] = help_menu
785 self.mainmenu.Append(help_menu, _("&Help"))
786
787
788
789 self.SetMenuBar(self.mainmenu)
790
793
794
795
797 """register events we want to react to"""
798
799 wx.EVT_CLOSE(self, self.OnClose)
800 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
801 wx.EVT_END_SESSION(self, self._on_end_session)
802
803 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
804 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
805 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
806 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
807 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
808 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
809 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
810 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
811
812 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
813
814 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
815
816 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
817
818 _log.debug('registering plugin with menu system')
819 _log.debug(' generic name: %s', plugin_name)
820 _log.debug(' class name: %s', class_name)
821 _log.debug(' specific menu: %s', menu_name)
822 _log.debug(' menu item: %s', menu_item_name)
823
824
825 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
826 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
827 self.menu_id2plugin[item.Id] = class_name
828
829
830 if menu_name is not None:
831 menu = self.__gb['main.%smenu' % menu_name]
832 item = menu.Append(-1, menu_item_name, menu_help_string)
833 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
834 self.menu_id2plugin[item.Id] = class_name
835
836 return True
837
839 gmDispatcher.send (
840 signal = u'display_widget',
841 name = self.menu_id2plugin[evt.Id]
842 )
843
845 wx.Bell()
846 wx.Bell()
847 wx.Bell()
848 _log.warning('unhandled event detected: QUERY_END_SESSION')
849 _log.info('we should be saving ourselves from here')
850 gmLog2.flush()
851 print "unhandled event detected: QUERY_END_SESSION"
852
854 wx.Bell()
855 wx.Bell()
856 wx.Bell()
857 _log.warning('unhandled event detected: END_SESSION')
858 gmLog2.flush()
859 print "unhandled event detected: END_SESSION"
860
862 if not callable(callback):
863 raise TypeError(u'callback [%s] not callable' % callback)
864
865 self.__pre_exit_callbacks.append(callback)
866
867 - def _on_set_statustext_pubsub(self, context=None):
868 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
869 wx.CallAfter(self.SetStatusText, msg)
870
871 try:
872 if context.data['beep']:
873 wx.Bell()
874 except KeyError:
875 pass
876
877 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
878
879 if msg is None:
880 msg = _('programmer forgot to specify status message')
881
882 if loglevel is not None:
883 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
884
885 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
886 wx.CallAfter(self.SetStatusText, msg)
887
888 if beep:
889 wx.Bell()
890
892 wx.CallAfter(self.__on_db_maintenance_warning)
893
895
896 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
897 wx.Bell()
898 if not wx.GetApp().IsActive():
899 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
900
901 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
902
903 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
904 None,
905 -1,
906 caption = _('Database shutdown warning'),
907 question = _(
908 'The database will be shut down for maintenance\n'
909 'in a few minutes.\n'
910 '\n'
911 'In order to not suffer any loss of data you\n'
912 'will need to save your current work and log\n'
913 'out of this GNUmed client.\n'
914 ),
915 button_defs = [
916 {
917 u'label': _('Close now'),
918 u'tooltip': _('Close this GNUmed client immediately.'),
919 u'default': False
920 },
921 {
922 u'label': _('Finish work'),
923 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
924 u'default': True
925 }
926 ]
927 )
928 decision = dlg.ShowModal()
929 if decision == wx.ID_YES:
930 top_win = wx.GetApp().GetTopWindow()
931 wx.CallAfter(top_win.Close)
932
934 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
935
937
938 if not wx.GetApp().IsActive():
939 if urgent:
940 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
941 else:
942 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
943
944 if msg is not None:
945 self.SetStatusText(msg)
946
947 if urgent:
948 wx.Bell()
949
950 gmHooks.run_hook_script(hook = u'request_user_attention')
951
953 wx.CallAfter(self.__on_pat_name_changed)
954
956 self.__update_window_title()
957
959 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
960
962 self.__update_window_title()
963 try:
964 gmHooks.run_hook_script(hook = u'post_patient_activation')
965 except:
966 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
967 raise
968
970 return self.__sanity_check_encounter()
971
1029
1030
1031
1034
1042
1045
1046
1047
1062
1085
1087 from Gnumed.wxpython import gmAbout
1088 contribs = gmAbout.cContributorsDlg (
1089 parent = self,
1090 id = -1,
1091 title = _('GNUmed contributors'),
1092 size = wx.Size(400,600),
1093 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1094 )
1095 contribs.ShowModal()
1096 del contribs
1097 del gmAbout
1098
1099
1100
1102 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1103 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1104 self.Close(True)
1105 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1106
1109
1111 send = gmGuiHelpers.gm_show_question (
1112 _('This will send a notification about database downtime\n'
1113 'to all GNUmed clients connected to your database.\n'
1114 '\n'
1115 'Do you want to send the notification ?\n'
1116 ),
1117 _('Announcing database maintenance downtime')
1118 )
1119 if not send:
1120 return
1121 gmPG2.send_maintenance_notification()
1122
1123
1126
1127
1128
1160
1173
1174 gmCfgWidgets.configure_string_option (
1175 message = _(
1176 'Some network installations cannot cope with loading\n'
1177 'documents of arbitrary size in one piece from the\n'
1178 'database (mainly observed on older Windows versions)\n.'
1179 '\n'
1180 'Under such circumstances documents need to be retrieved\n'
1181 'in chunks and reassembled on the client.\n'
1182 '\n'
1183 'Here you can set the size (in Bytes) above which\n'
1184 'GNUmed will retrieve documents in chunks. Setting this\n'
1185 'value to 0 will disable the chunking protocol.'
1186 ),
1187 option = 'horstspace.blob_export_chunk_size',
1188 bias = 'workplace',
1189 default_value = 1024 * 1024,
1190 validator = is_valid
1191 )
1192
1193
1194
1262
1266
1267
1268
1277
1278 gmCfgWidgets.configure_string_option (
1279 message = _(
1280 'When GNUmed cannot find an OpenOffice server it\n'
1281 'will try to start one. OpenOffice, however, needs\n'
1282 'some time to fully start up.\n'
1283 '\n'
1284 'Here you can set the time for GNUmed to wait for OOo.\n'
1285 ),
1286 option = 'external.ooo.startup_settle_time',
1287 bias = 'workplace',
1288 default_value = 2.0,
1289 validator = is_valid
1290 )
1291
1294
1306
1307 gmCfgWidgets.configure_string_option (
1308 message = _(
1309 'GNUmed will use this URL to access a website which lets\n'
1310 'you report an adverse drug reaction (ADR).\n'
1311 '\n'
1312 'You can leave this empty but to set it to a specific\n'
1313 'address the URL must be accessible now.'
1314 ),
1315 option = 'external.urls.report_ADR',
1316 bias = 'user',
1317 default_value = u'https://dcgma.org/uaw/meldung.php',
1318 validator = is_valid
1319 )
1320
1332
1333 gmCfgWidgets.configure_string_option (
1334 message = _(
1335 'GNUmed will use this URL to access a website which lets\n'
1336 'you report an adverse vaccination reaction (vADR).\n'
1337 '\n'
1338 'If you set it to a specific address that URL must be\n'
1339 'accessible now. If you leave it empty it will fall back\n'
1340 'to the URL for reporting other adverse drug reactions.'
1341 ),
1342 option = 'external.urls.report_vaccine_ADR',
1343 bias = 'user',
1344 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',
1345 validator = is_valid
1346 )
1347
1359
1360 gmCfgWidgets.configure_string_option (
1361 message = _(
1362 'GNUmed will use this URL to access an encyclopedia of\n'
1363 'measurement/lab methods from within the measurments grid.\n'
1364 '\n'
1365 'You can leave this empty but to set it to a specific\n'
1366 'address the URL must be accessible now.'
1367 ),
1368 option = 'external.urls.measurements_encyclopedia',
1369 bias = 'user',
1370 default_value = u'http://www.laborlexikon.de',
1371 validator = is_valid
1372 )
1373
1385
1386 gmCfgWidgets.configure_string_option (
1387 message = _(
1388 'GNUmed will use this URL to access a page showing\n'
1389 'vaccination schedules.\n'
1390 '\n'
1391 'You can leave this empty but to set it to a specific\n'
1392 'address the URL must be accessible now.'
1393 ),
1394 option = 'external.urls.vaccination_plans',
1395 bias = 'user',
1396 default_value = u'http://www.bundesaerztekammer.de/downloads/ImpfempfehlungenRKI2009.pdf',
1397 validator = is_valid
1398 )
1399
1412
1413 gmCfgWidgets.configure_string_option (
1414 message = _(
1415 'Enter the shell command with which to start the\n'
1416 'the ACS risk assessment calculator.\n'
1417 '\n'
1418 'GNUmed will try to verify the path which may,\n'
1419 'however, fail if you are using an emulator such\n'
1420 'as Wine. Nevertheless, starting the calculator\n'
1421 'will work as long as the shell command is correct\n'
1422 'despite the failing test.'
1423 ),
1424 option = 'external.tools.acs_risk_calculator_cmd',
1425 bias = 'user',
1426 validator = is_valid
1427 )
1428
1431
1444
1445 gmCfgWidgets.configure_string_option (
1446 message = _(
1447 'Enter the shell command with which to start\n'
1448 'the FreeDiams drug database frontend.\n'
1449 '\n'
1450 'GNUmed will try to verify that path.'
1451 ),
1452 option = 'external.tools.freediams_cmd',
1453 bias = 'workplace',
1454 default_value = None,
1455 validator = is_valid
1456 )
1457
1470
1471 gmCfgWidgets.configure_string_option (
1472 message = _(
1473 'Enter the shell command with which to start the\n'
1474 'the IFAP drug database.\n'
1475 '\n'
1476 'GNUmed will try to verify the path which may,\n'
1477 'however, fail if you are using an emulator such\n'
1478 'as Wine. Nevertheless, starting IFAP will work\n'
1479 'as long as the shell command is correct despite\n'
1480 'the failing test.'
1481 ),
1482 option = 'external.ifap-win.shell_command',
1483 bias = 'workplace',
1484 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1485 validator = is_valid
1486 )
1487
1488
1489
1538
1539
1540
1557
1560
1563
1568
1569 gmCfgWidgets.configure_string_option (
1570 message = _(
1571 'When a patient is activated GNUmed checks the\n'
1572 "proximity of the patient's birthday.\n"
1573 '\n'
1574 'If the birthday falls within the range of\n'
1575 ' "today %s <the interval you set here>"\n'
1576 'GNUmed will remind you of the recent or\n'
1577 'imminent anniversary.'
1578 ) % u'\u2213',
1579 option = u'patient_search.dob_warn_interval',
1580 bias = 'user',
1581 default_value = '1 week',
1582 validator = is_valid
1583 )
1584
1586
1587 gmCfgWidgets.configure_boolean_option (
1588 parent = self,
1589 question = _(
1590 'When adding progress notes do you want to\n'
1591 'allow opening several unassociated, new\n'
1592 'episodes for a patient at once ?\n'
1593 '\n'
1594 'This can be particularly helpful when entering\n'
1595 'progress notes on entirely new patients presenting\n'
1596 'with a multitude of problems on their first visit.'
1597 ),
1598 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1599 button_tooltips = [
1600 _('Yes, allow for multiple new episodes concurrently.'),
1601 _('No, only allow editing one new episode at a time.')
1602 ]
1603 )
1604
1650
1651
1652
1655
1658
1672
1674 gmCfgWidgets.configure_boolean_option (
1675 parent = self,
1676 question = _(
1677 'Do you want GNUmed to show the encounter\n'
1678 'details editor when changing the active patient ?'
1679 ),
1680 option = 'encounter.show_editor_before_patient_change',
1681 button_tooltips = [
1682 _('Yes, show the encounter editor if it seems appropriate.'),
1683 _('No, never show the encounter editor even if it would seem useful.')
1684 ]
1685 )
1686
1691
1692 gmCfgWidgets.configure_string_option (
1693 message = _(
1694 'When a patient is activated GNUmed checks the\n'
1695 'chart for encounters lacking any entries.\n'
1696 '\n'
1697 'Any such encounters older than what you set\n'
1698 'here will be removed from the medical record.\n'
1699 '\n'
1700 'To effectively disable removal of such encounters\n'
1701 'set this option to an improbable value.\n'
1702 ),
1703 option = 'encounter.ttl_if_empty',
1704 bias = 'user',
1705 default_value = '1 week',
1706 validator = is_valid
1707 )
1708
1713
1714 gmCfgWidgets.configure_string_option (
1715 message = _(
1716 'When a patient is activated GNUmed checks the\n'
1717 'age of the most recent encounter.\n'
1718 '\n'
1719 'If that encounter is younger than this age\n'
1720 'the existing encounter will be continued.\n'
1721 '\n'
1722 '(If it is really old a new encounter is\n'
1723 ' started, or else GNUmed will ask you.)\n'
1724 ),
1725 option = 'encounter.minimum_ttl',
1726 bias = 'user',
1727 default_value = '1 hour 30 minutes',
1728 validator = is_valid
1729 )
1730
1735
1736 gmCfgWidgets.configure_string_option (
1737 message = _(
1738 'When a patient is activated GNUmed checks the\n'
1739 'age of the most recent encounter.\n'
1740 '\n'
1741 'If that encounter is older than this age\n'
1742 'GNUmed will always start a new encounter.\n'
1743 '\n'
1744 '(If it is very recent the existing encounter\n'
1745 ' is continued, or else GNUmed will ask you.)\n'
1746 ),
1747 option = 'encounter.maximum_ttl',
1748 bias = 'user',
1749 default_value = '6 hours',
1750 validator = is_valid
1751 )
1752
1761
1762 gmCfgWidgets.configure_string_option (
1763 message = _(
1764 'At any time there can only be one open (ongoing)\n'
1765 'episode for each health issue.\n'
1766 '\n'
1767 'When you try to open (add data to) an episode on a health\n'
1768 'issue GNUmed will check for an existing open episode on\n'
1769 'that issue. If there is any it will check the age of that\n'
1770 'episode. The episode is closed if it has been dormant (no\n'
1771 'data added, that is) for the period of time (in days) you\n'
1772 'set here.\n'
1773 '\n'
1774 "If the existing episode hasn't been dormant long enough\n"
1775 'GNUmed will consult you what to do.\n'
1776 '\n'
1777 'Enter maximum episode dormancy in DAYS:'
1778 ),
1779 option = 'episode.ttl',
1780 bias = 'user',
1781 default_value = 60,
1782 validator = is_valid
1783 )
1784
1815
1818
1833
1858
1870
1871 gmCfgWidgets.configure_string_option (
1872 message = _(
1873 'GNUmed can check for new releases being available. To do\n'
1874 'so it needs to load version information from an URL.\n'
1875 '\n'
1876 'The default URL is:\n'
1877 '\n'
1878 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1879 '\n'
1880 'but you can configure any other URL locally. Note\n'
1881 'that you must enter the location as a valid URL.\n'
1882 'Depending on the URL the client will need online\n'
1883 'access when checking for updates.'
1884 ),
1885 option = u'horstspace.update.url',
1886 bias = u'workplace',
1887 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1888 validator = is_valid
1889 )
1890
1908
1925
1936
1937 gmCfgWidgets.configure_string_option (
1938 message = _(
1939 'GNUmed can show the document review dialog after\n'
1940 'calling the appropriate viewer for that document.\n'
1941 '\n'
1942 'Select the conditions under which you want\n'
1943 'GNUmed to do so:\n'
1944 '\n'
1945 ' 0: never display the review dialog\n'
1946 ' 1: always display the dialog\n'
1947 ' 2: only if there is no previous review by me\n'
1948 '\n'
1949 'Note that if a viewer is configured to not block\n'
1950 'GNUmed during document display the review dialog\n'
1951 'will actually appear in parallel to the viewer.'
1952 ),
1953 option = u'horstspace.document_viewer.review_after_display',
1954 bias = u'user',
1955 default_value = 2,
1956 validator = is_valid
1957 )
1958
1972
1974
1975 dbcfg = gmCfg.cCfgSQL()
1976 cmd = dbcfg.get2 (
1977 option = u'external.tools.acs_risk_calculator_cmd',
1978 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1979 bias = 'user'
1980 )
1981
1982 if cmd is None:
1983 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
1984 return
1985
1986 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1987 try:
1988 subprocess.check_call (
1989 args = (cmd,),
1990 close_fds = True,
1991 cwd = cwd
1992 )
1993 except (OSError, ValueError, subprocess.CalledProcessError):
1994 _log.exception('there was a problem executing [%s]', cmd)
1995 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
1996 return
1997
1998 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
1999 for pdf in pdfs:
2000 try:
2001 open(pdf).close()
2002 except:
2003 _log.exception('error accessing [%s]', pdf)
2004 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the ARRIBA result in [%s] !') % pdf, beep = True)
2005 continue
2006
2007 doc = gmDocumentWidgets.save_file_as_new_document (
2008 parent = self,
2009 filename = pdf,
2010 document_type = u'risk assessment'
2011 )
2012
2013 try:
2014 os.remove(pdf)
2015 except StandardError:
2016 _log.exception('cannot remove [%s]', pdf)
2017
2018 if doc is None:
2019 continue
2020 doc['comment'] = u'ARRIBA: %s' % _('cardiovascular risk assessment')
2021 doc.save()
2022
2023 return
2024
2026 dlg = gmSnellen.cSnellenCfgDlg()
2027 if dlg.ShowModal() != wx.ID_OK:
2028 return
2029
2030 frame = gmSnellen.cSnellenChart (
2031 width = dlg.vals[0],
2032 height = dlg.vals[1],
2033 alpha = dlg.vals[2],
2034 mirr = dlg.vals[3],
2035 parent = None
2036 )
2037 frame.CentreOnScreen(wx.BOTH)
2038
2039
2040 frame.Show(True)
2041
2042
2044 webbrowser.open (
2045 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
2046 new = False,
2047 autoraise = True
2048 )
2049
2052
2054 webbrowser.open (
2055 url = 'http://www.kompendium.ch',
2056 new = False,
2057 autoraise = True
2058 )
2059
2060
2061
2065
2066
2067
2069 wx.CallAfter(self.__save_screenshot)
2070 evt.Skip()
2071
2073
2074 time.sleep(0.5)
2075
2076 rect = self.GetRect()
2077
2078
2079 if sys.platform == 'linux2':
2080 client_x, client_y = self.ClientToScreen((0, 0))
2081 border_width = client_x - rect.x
2082 title_bar_height = client_y - rect.y
2083
2084 if self.GetMenuBar():
2085 title_bar_height /= 2
2086 rect.width += (border_width * 2)
2087 rect.height += title_bar_height + border_width
2088
2089 wdc = wx.ScreenDC()
2090 mdc = wx.MemoryDC()
2091 img = wx.EmptyBitmap(rect.width, rect.height)
2092 mdc.SelectObject(img)
2093 mdc.Blit (
2094 0, 0,
2095 rect.width, rect.height,
2096 wdc,
2097 rect.x, rect.y
2098 )
2099
2100
2101 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2102 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2103 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2104
2106
2107 raise ValueError('raised ValueError to test exception handling')
2108
2110 import wx.lib.inspection
2111 wx.lib.inspection.InspectionTool().Show()
2112
2114 webbrowser.open (
2115 url = 'https://bugs.launchpad.net/gnumed/',
2116 new = False,
2117 autoraise = True
2118 )
2119
2121 webbrowser.open (
2122 url = 'http://wiki.gnumed.de',
2123 new = False,
2124 autoraise = True
2125 )
2126
2128 webbrowser.open (
2129 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
2130 new = False,
2131 autoraise = True
2132 )
2133
2135 webbrowser.open (
2136 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
2137 new = False,
2138 autoraise = True
2139 )
2140
2147
2151
2154
2161
2166
2168 name = os.path.basename(gmLog2._logfile_name)
2169 name, ext = os.path.splitext(name)
2170 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2171 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2172
2173 dlg = wx.FileDialog (
2174 parent = self,
2175 message = _("Save current log as..."),
2176 defaultDir = new_path,
2177 defaultFile = new_name,
2178 wildcard = "%s (*.log)|*.log" % _("log files"),
2179 style = wx.SAVE
2180 )
2181 choice = dlg.ShowModal()
2182 new_name = dlg.GetPath()
2183 dlg.Destroy()
2184 if choice != wx.ID_OK:
2185 return True
2186
2187 _log.warning('syncing log file for backup to [%s]', new_name)
2188 gmLog2.flush()
2189 shutil.copy2(gmLog2._logfile_name, new_name)
2190 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2191
2194
2195
2196
2198 """This is the wx.EVT_CLOSE handler.
2199
2200 - framework still functional
2201 """
2202 _log.debug('gmTopLevelFrame.OnClose() start')
2203 self._clean_exit()
2204 self.Destroy()
2205 _log.debug('gmTopLevelFrame.OnClose() end')
2206 return True
2207
2213
2218
2226
2233
2240
2250
2258
2266
2274
2282
2291
2299
2301 pat = gmPerson.gmCurrentPatient()
2302 if not pat.connected:
2303 gmDispatcher.send(signal = 'statustext', msg = _('Cannot show EMR summary. No active patient.'))
2304 return False
2305
2306 emr = pat.get_emr()
2307 dlg = wx.MessageDialog (
2308 parent = self,
2309 message = emr.format_statistics(),
2310 caption = _('EMR Summary'),
2311 style = wx.OK | wx.STAY_ON_TOP
2312 )
2313 dlg.ShowModal()
2314 dlg.Destroy()
2315 return True
2316
2319
2322
2324
2325 pat = gmPerson.gmCurrentPatient()
2326 if not pat.connected:
2327 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2328 return False
2329
2330 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2331
2332 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2333 gmTools.mkdir(aDefDir)
2334
2335 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2336 dlg = wx.FileDialog (
2337 parent = self,
2338 message = _("Save patient's EMR journal as..."),
2339 defaultDir = aDefDir,
2340 defaultFile = fname,
2341 wildcard = aWildcard,
2342 style = wx.SAVE
2343 )
2344 choice = dlg.ShowModal()
2345 fname = dlg.GetPath()
2346 dlg.Destroy()
2347 if choice != wx.ID_OK:
2348 return True
2349
2350 _log.debug('exporting EMR journal to [%s]' % fname)
2351
2352 exporter = gmPatientExporter.cEMRJournalExporter()
2353
2354 wx.BeginBusyCursor()
2355 try:
2356 fname = exporter.export_to_file(filename = fname)
2357 except:
2358 wx.EndBusyCursor()
2359 gmGuiHelpers.gm_show_error (
2360 _('Error exporting patient EMR as chronological journal.'),
2361 _('EMR journal export')
2362 )
2363 raise
2364 wx.EndBusyCursor()
2365
2366 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2367
2368 return True
2369
2376
2386
2388 curr_pat = gmPerson.gmCurrentPatient()
2389 if not curr_pat.connected:
2390 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2391 return False
2392
2393 enc = 'cp850'
2394 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2395 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2396 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2397
2400
2401
2402
2403
2404
2405
2406
2414
2422
2425
2432
2436
2440
2443
2446
2449
2452
2455
2458
2461
2464
2467
2470
2473
2476
2479
2482
2485
2490
2492 """Cleanup helper.
2493
2494 - should ALWAYS be called when this program is
2495 to be terminated
2496 - ANY code that should be executed before a
2497 regular shutdown should go in here
2498 - framework still functional
2499 """
2500 _log.debug('gmTopLevelFrame._clean_exit() start')
2501
2502
2503 listener = gmBackendListener.gmBackendListener()
2504 try:
2505 listener.shutdown()
2506 except:
2507 _log.exception('cannot stop backend notifications listener thread')
2508
2509
2510 if _scripting_listener is not None:
2511 try:
2512 _scripting_listener.shutdown()
2513 except:
2514 _log.exception('cannot stop scripting listener thread')
2515
2516
2517 self.clock_update_timer.Stop()
2518 gmTimer.shutdown()
2519 gmPhraseWheel.shutdown()
2520
2521
2522 for call_back in self.__pre_exit_callbacks:
2523 try:
2524 call_back()
2525 except:
2526 print "*** pre-exit callback failed ***"
2527 print call_back
2528 _log.exception('callback [%s] failed', call_back)
2529
2530
2531 gmDispatcher.send(u'application_closing')
2532
2533
2534 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2535
2536
2537 curr_width, curr_height = self.GetClientSizeTuple()
2538 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2539 dbcfg = gmCfg.cCfgSQL()
2540 dbcfg.set (
2541 option = 'main.window.width',
2542 value = curr_width,
2543 workplace = gmSurgery.gmCurrentPractice().active_workplace
2544 )
2545 dbcfg.set (
2546 option = 'main.window.height',
2547 value = curr_height,
2548 workplace = gmSurgery.gmCurrentPractice().active_workplace
2549 )
2550
2551 if _cfg.get(option = 'debug'):
2552 print '---=== GNUmed shutdown ===---'
2553 try:
2554 print _('You have to manually close this window to finalize shutting down GNUmed.')
2555 print _('This is so that you can inspect the console output at your leisure.')
2556 except UnicodeEncodeError:
2557 print 'You have to manually close this window to finalize shutting down GNUmed.'
2558 print 'This is so that you can inspect the console output at your leisure.'
2559 print '---=== GNUmed shutdown ===---'
2560
2561
2562 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2563
2564
2565 import threading
2566 _log.debug("%s active threads", threading.activeCount())
2567 for t in threading.enumerate():
2568 _log.debug('thread %s', t)
2569
2570 _log.debug('gmTopLevelFrame._clean_exit() end')
2571
2572
2573
2575
2576 if _cfg.get(option = 'slave'):
2577 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2578 _cfg.get(option = 'slave personality'),
2579 _cfg.get(option = 'xml-rpc port')
2580 )
2581 else:
2582 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2583
2585 """Update title of main window based on template.
2586
2587 This gives nice tooltips on iconified GNUmed instances.
2588
2589 User research indicates that in the title bar people want
2590 the date of birth, not the age, so please stick to this
2591 convention.
2592 """
2593 args = {}
2594
2595 pat = gmPerson.gmCurrentPatient()
2596 if pat.connected:
2597
2598
2599
2600
2601
2602
2603 args['pat'] = u'%s %s %s (%s) #%d' % (
2604 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2605
2606 pat['firstnames'],
2607 pat['lastnames'],
2608 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2609 pat['pk_identity']
2610 )
2611 else:
2612 args['pat'] = _('no patient')
2613
2614 args['prov'] = u'%s%s.%s' % (
2615 gmTools.coalesce(_provider['title'], u'', u'%s '),
2616 _provider['firstnames'][:1],
2617 _provider['lastnames']
2618 )
2619
2620 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2621
2622 self.SetTitle(self.__title_template % args)
2623
2624
2626 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2627 sb.SetStatusWidths([-1, 225])
2628
2629 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2630 self._cb_update_clock()
2631
2632 self.clock_update_timer.Start(milliseconds = 1000)
2633
2635 """Displays date and local time in the second slot of the status bar"""
2636 t = time.localtime(time.time())
2637 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2638 self.SetStatusText(st,1)
2639
2641 """Lock GNUmed client against unauthorized access"""
2642
2643
2644
2645 return
2646
2648 """Unlock the main notebook widgets
2649 As long as we are not logged into the database backend,
2650 all pages but the 'login' page of the main notebook widget
2651 are locked; i.e. not accessible by the user
2652 """
2653
2654
2655
2656
2657
2658 return
2659
2661 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2662
2664
2666
2667 self.__starting_up = True
2668
2669 gmExceptionHandlingWidgets.install_wx_exception_handler()
2670 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2671
2672
2673
2674
2675 self.SetAppName(u'gnumed')
2676 self.SetVendorName(u'The GNUmed Development Community.')
2677 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2678 paths.init_paths(wx = wx, app_name = u'gnumed')
2679
2680 if not self.__setup_prefs_file():
2681 return False
2682
2683 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2684
2685 self.__guibroker = gmGuiBroker.GuiBroker()
2686 self.__setup_platform()
2687
2688 if not self.__establish_backend_connection():
2689 return False
2690
2691 if not _cfg.get(option = 'skip-update-check'):
2692 self.__check_for_updates()
2693
2694 if _cfg.get(option = 'slave'):
2695 if not self.__setup_scripting_listener():
2696 return False
2697
2698
2699 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640,440))
2700 frame.CentreOnScreen(wx.BOTH)
2701 self.SetTopWindow(frame)
2702 frame.Show(True)
2703
2704 if _cfg.get(option = 'debug'):
2705 self.RedirectStdio()
2706 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2707
2708
2709 print '---=== GNUmed startup ===---'
2710 print _('redirecting STDOUT/STDERR to this log window')
2711 print '---=== GNUmed startup ===---'
2712
2713 self.__setup_user_activity_timer()
2714 self.__register_events()
2715
2716 wx.CallAfter(self._do_after_init)
2717
2718 return True
2719
2721 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2722
2723 - after destroying all application windows and controls
2724 - before wx.Windows internal cleanup
2725 """
2726 _log.debug('gmApp.OnExit() start')
2727
2728 self.__shutdown_user_activity_timer()
2729
2730 if _cfg.get(option = 'debug'):
2731 self.RestoreStdio()
2732 sys.stdin = sys.__stdin__
2733 sys.stdout = sys.__stdout__
2734 sys.stderr = sys.__stderr__
2735
2736 _log.debug('gmApp.OnExit() end')
2737
2739 wx.Bell()
2740 wx.Bell()
2741 wx.Bell()
2742 _log.warning('unhandled event detected: QUERY_END_SESSION')
2743 _log.info('we should be saving ourselves from here')
2744 gmLog2.flush()
2745 print "unhandled event detected: QUERY_END_SESSION"
2746
2748 wx.Bell()
2749 wx.Bell()
2750 wx.Bell()
2751 _log.warning('unhandled event detected: END_SESSION')
2752 gmLog2.flush()
2753 print "unhandled event detected: END_SESSION"
2754
2765
2767 self.user_activity_detected = True
2768 evt.Skip()
2769
2771
2772 if self.user_activity_detected:
2773 self.elapsed_inactivity_slices = 0
2774 self.user_activity_detected = False
2775 self.elapsed_inactivity_slices += 1
2776 else:
2777 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2778
2779 pass
2780
2781 self.user_activity_timer.Start(oneShot = True)
2782
2783
2784
2786 try:
2787 kwargs['originated_in_database']
2788 print '==> got notification from database "%s":' % kwargs['signal']
2789 except KeyError:
2790 print '==> received signal from client: "%s"' % kwargs['signal']
2791
2792 del kwargs['signal']
2793 for key in kwargs.keys():
2794 print ' [%s]: %s' % (key, kwargs[key])
2795
2797 print "wx.lib.pubsub message:"
2798 print msg.topic
2799 print msg.data
2800
2806
2808 self.user_activity_detected = True
2809 self.elapsed_inactivity_slices = 0
2810
2811 self.max_user_inactivity_slices = 15
2812 self.user_activity_timer = gmTimer.cTimer (
2813 callback = self._on_user_activity_timer_expired,
2814 delay = 2000
2815 )
2816 self.user_activity_timer.Start(oneShot=True)
2817
2819 try:
2820 self.user_activity_timer.Stop()
2821 del self.user_activity_timer
2822 except:
2823 pass
2824
2826 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2827 wx.EVT_END_SESSION(self, self._on_end_session)
2828
2829
2830
2831
2832
2833 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2834
2835 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2836 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2862
2864 """Handle all the database related tasks necessary for startup."""
2865
2866
2867 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
2868
2869 from Gnumed.wxpython import gmAuthWidgets
2870 connected = gmAuthWidgets.connect_to_database (
2871 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
2872 require_version = not override
2873 )
2874 if not connected:
2875 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
2876 return False
2877
2878
2879 try:
2880 global _provider
2881 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
2882 except ValueError:
2883 account = gmPG2.get_current_user()
2884 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
2885 msg = _(
2886 'The database account [%s] cannot be used as a\n'
2887 'staff member login for GNUmed. There was an\n'
2888 'error retrieving staff details for it.\n\n'
2889 'Please ask your administrator for help.\n'
2890 ) % account
2891 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
2892 return False
2893
2894
2895 tmp = '%s%s %s (%s = %s)' % (
2896 gmTools.coalesce(_provider['title'], ''),
2897 _provider['firstnames'],
2898 _provider['lastnames'],
2899 _provider['short_alias'],
2900 _provider['db_user']
2901 )
2902 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
2903
2904
2905 surgery = gmSurgery.gmCurrentPractice()
2906 msg = surgery.db_logon_banner
2907 if msg.strip() != u'':
2908
2909 login = gmPG2.get_default_login()
2910 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
2911 login.database,
2912 gmTools.coalesce(login.host, u'localhost')
2913 ))
2914 msg = auth + msg + u'\n\n'
2915
2916 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
2917 None,
2918 -1,
2919 caption = _('Verifying database'),
2920 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
2921 button_defs = [
2922 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
2923 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
2924 ]
2925 )
2926 go_on = dlg.ShowModal()
2927 dlg.Destroy()
2928 if go_on != wx.ID_YES:
2929 _log.info('user decided to not connect to this database')
2930 return False
2931
2932
2933 self.__check_db_lang()
2934
2935 return True
2936
2938 """Setup access to a config file for storing preferences."""
2939
2940 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2941
2942 candidates = []
2943 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
2944 if explicit_file is not None:
2945 candidates.append(explicit_file)
2946
2947 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
2948 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
2949 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
2950
2951 prefs_file = None
2952 for candidate in candidates:
2953 try:
2954 open(candidate, 'a+').close()
2955 prefs_file = candidate
2956 break
2957 except IOError:
2958 continue
2959
2960 if prefs_file is None:
2961 msg = _(
2962 'Cannot find configuration file in any of:\n'
2963 '\n'
2964 ' %s\n'
2965 'You may need to use the comand line option\n'
2966 '\n'
2967 ' --conf-file=<FILE>'
2968 ) % '\n '.join(candidates)
2969 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
2970 return False
2971
2972 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
2973 _log.info('user preferences file: %s', prefs_file)
2974
2975 return True
2976
2978
2979 from socket import error as SocketError
2980 from Gnumed.pycommon import gmScriptingListener
2981 from Gnumed.wxpython import gmMacro
2982
2983 slave_personality = gmTools.coalesce (
2984 _cfg.get (
2985 group = u'workplace',
2986 option = u'slave personality',
2987 source_order = [
2988 ('explicit', 'return'),
2989 ('workbase', 'return'),
2990 ('user', 'return'),
2991 ('system', 'return')
2992 ]
2993 ),
2994 u'gnumed-client'
2995 )
2996 _cfg.set_option(option = 'slave personality', value = slave_personality)
2997
2998
2999 port = int (
3000 gmTools.coalesce (
3001 _cfg.get (
3002 group = u'workplace',
3003 option = u'xml-rpc port',
3004 source_order = [
3005 ('explicit', 'return'),
3006 ('workbase', 'return'),
3007 ('user', 'return'),
3008 ('system', 'return')
3009 ]
3010 ),
3011 9999
3012 )
3013 )
3014 _cfg.set_option(option = 'xml-rpc port', value = port)
3015
3016 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3017 global _scripting_listener
3018 try:
3019 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3020 except SocketError, e:
3021 _log.exception('cannot start GNUmed XML-RPC server')
3022 gmGuiHelpers.gm_show_error (
3023 aMessage = (
3024 'Cannot start the GNUmed server:\n'
3025 '\n'
3026 ' [%s]'
3027 ) % e,
3028 aTitle = _('GNUmed startup')
3029 )
3030 return False
3031
3032 return True
3033
3054
3056 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3057 _log.warning("system locale is undefined (probably meaning 'C')")
3058 return True
3059
3060
3061 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3062 db_lang = rows[0]['lang']
3063
3064 if db_lang is None:
3065 _log.debug("database locale currently not set")
3066 msg = _(
3067 "There is no language selected in the database for user [%s].\n"
3068 "Your system language is currently set to [%s].\n\n"
3069 "Do you want to set the database language to '%s' ?\n\n"
3070 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3071 checkbox_msg = _('Remember to ignore missing language')
3072 else:
3073 _log.debug("current database locale: [%s]" % db_lang)
3074 msg = _(
3075 "The currently selected database language ('%s') does\n"
3076 "not match the current system language ('%s').\n"
3077 "\n"
3078 "Do you want to set the database language to '%s' ?\n"
3079 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3080 checkbox_msg = _('Remember to ignore language mismatch')
3081
3082
3083 if db_lang == gmI18N.system_locale_level['full']:
3084 _log.debug('Database locale (%s) up to date.' % db_lang)
3085 return True
3086 if db_lang == gmI18N.system_locale_level['country']:
3087 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3088 return True
3089 if db_lang == gmI18N.system_locale_level['language']:
3090 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3091 return True
3092
3093 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3094
3095
3096 ignored_sys_lang = _cfg.get (
3097 group = u'backend',
3098 option = u'ignored mismatching system locale',
3099 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3100 )
3101
3102
3103 if gmI18N.system_locale == ignored_sys_lang:
3104 _log.info('configured to ignore system-to-database locale mismatch')
3105 return True
3106
3107
3108 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3109 None,
3110 -1,
3111 caption = _('Checking database language settings'),
3112 question = msg,
3113 button_defs = [
3114 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3115 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3116 ],
3117 show_checkbox = True,
3118 checkbox_msg = checkbox_msg,
3119 checkbox_tooltip = _(
3120 'Checking this will make GNUmed remember your decision\n'
3121 'until the system language is changed.\n'
3122 '\n'
3123 'You can also reactivate this inquiry by removing the\n'
3124 'corresponding "ignore" option from the configuration file\n'
3125 '\n'
3126 ' [%s]'
3127 ) % _cfg.get(option = 'user_preferences_file')
3128 )
3129 decision = dlg.ShowModal()
3130 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3131 dlg.Destroy()
3132
3133 if decision == wx.ID_NO:
3134 if not remember_ignoring_problem:
3135 return True
3136 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3137 gmCfg2.set_option_in_INI_file (
3138 filename = _cfg.get(option = 'user_preferences_file'),
3139 group = 'backend',
3140 option = 'ignored mismatching system locale',
3141 value = gmI18N.system_locale
3142 )
3143 return True
3144
3145
3146 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3147 if len(lang) > 0:
3148
3149
3150 rows, idx = gmPG2.run_rw_queries (
3151 link_obj = None,
3152 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3153 return_data = True
3154 )
3155 if rows[0][0]:
3156 _log.debug("Successfully set database language to [%s]." % lang)
3157 else:
3158 _log.error('Cannot set database language to [%s].' % lang)
3159 continue
3160 return True
3161
3162
3163 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3164 gmPG2.run_rw_queries(queries = [{
3165 'cmd': u'select i18n.force_curr_lang(%s)',
3166 'args': [gmI18N.system_locale_level['country']]
3167 }])
3168
3169 return True
3170
3172 try:
3173 kwargs['originated_in_database']
3174 print '==> got notification from database "%s":' % kwargs['signal']
3175 except KeyError:
3176 print '==> received signal from client: "%s"' % kwargs['signal']
3177
3178 del kwargs['signal']
3179 for key in kwargs.keys():
3180
3181 try: print ' [%s]: %s' % (key, kwargs[key])
3182 except: print 'cannot print signal information'
3183
3185
3186 try:
3187 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3188 print ' data: %s' % msg.data
3189 print msg
3190 except: print 'problem printing pubsub message information'
3191
3193
3194 if _cfg.get(option = 'debug'):
3195 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3196 _log.debug('gmDispatcher signal monitor activated')
3197 wx.lib.pubsub.Publisher().subscribe (
3198 listener = _signal_debugging_monitor_pubsub,
3199 topic = wx.lib.pubsub.getStrAllTopics()
3200 )
3201 _log.debug('wx.lib.pubsub signal monitor activated')
3202
3203 wx.InitAllImageHandlers()
3204
3205
3206
3207 app = gmApp(redirect = False, clearSigInt = False)
3208 app.MainLoop()
3209
3210
3211
3212 if __name__ == '__main__':
3213
3214 from GNUmed.pycommon import gmI18N
3215 gmI18N.activate_locale()
3216 gmI18N.install_domain()
3217
3218 _log.info('Starting up as main module.')
3219 main()
3220
3221
3222