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 _log.error('cannot set font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), desired_font_face)
107 _log.debug('default font is ', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
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
368 menu_cfg_encounter = wx.Menu()
369 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
370
371 ID = wx.NewId()
372 menu_cfg_encounter.Append(ID, _('Edit on patient change'), _('Edit encounter details on changing of patients.'))
373 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
374
375 ID = wx.NewId()
376 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
377 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
378
379 ID = wx.NewId()
380 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
381 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
382
383 ID = wx.NewId()
384 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
385 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
386
387 ID = wx.NewId()
388 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
389 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
390
391
392 menu_cfg_episode = wx.Menu()
393 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
394
395 ID = wx.NewId()
396 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
397 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
398
399
400 menu_master_data = wx.Menu()
401 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
402
403 item = menu_master_data.Append(-1, _('Workplace profiles'), _('Manage the plugins to load per workplace.'))
404 self.Bind(wx.EVT_MENU, self.__on_configure_workplace, item)
405
406 menu_master_data.AppendSeparator()
407
408 item = menu_master_data.Append(-1, _('&Document types'), _('Manage the document types available in the system.'))
409 self.Bind(wx.EVT_MENU, self.__on_edit_doc_types, item)
410
411 item = menu_master_data.Append(-1, _('&Form templates'), _('Manage templates for forms and letters.'))
412 self.Bind(wx.EVT_MENU, self.__on_manage_form_templates, item)
413
414 item = menu_master_data.Append(-1, _('&Text expansions'), _('Manage keyword based text expansion macros.'))
415 self.Bind(wx.EVT_MENU, self.__on_manage_text_expansion, item)
416
417 menu_master_data.AppendSeparator()
418
419 item = menu_master_data.Append(-1, _('&Encounter types'), _('Manage encounter types.'))
420 self.Bind(wx.EVT_MENU, self.__on_manage_encounter_types, item)
421
422 item = menu_master_data.Append(-1, _('&Provinces'), _('Manage provinces (counties, territories, ...).'))
423 self.Bind(wx.EVT_MENU, self.__on_manage_provinces, item)
424
425 menu_master_data.AppendSeparator()
426
427 item = menu_master_data.Append(-1, _('Substances'), _('Manage substances in use.'))
428 self.Bind(wx.EVT_MENU, self.__on_manage_substances, item)
429
430 item = menu_master_data.Append(-1, _('Drugs'), _('Manage branded drugs.'))
431 self.Bind(wx.EVT_MENU, self.__on_manage_branded_drugs, item)
432
433 item = menu_master_data.Append(-1, _('Drug components'), _('Manage components of branded drugs.'))
434 self.Bind(wx.EVT_MENU, self.__on_manage_substances_in_brands, item)
435
436 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
437 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
438
439 menu_master_data.AppendSeparator()
440
441 item = menu_master_data.Append(-1, _('Diagnostic orgs'), _('Manage diagnostic organisations (path labs etc).'))
442 self.Bind(wx.EVT_MENU, self.__on_manage_test_orgs, item)
443
444 item = menu_master_data.Append(-1, _('&Test types'), _('Manage test/measurement types.'))
445 self.Bind(wx.EVT_MENU, self.__on_manage_test_types, item)
446
447 item = menu_master_data.Append(-1, _('&Meta test types'), _('Show meta test/measurement types.'))
448 self.Bind(wx.EVT_MENU, self.__on_manage_meta_test_types, item)
449
450 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
451 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
452
453 menu_master_data.AppendSeparator()
454
455 item = menu_master_data.Append(-1, _('Vaccines'), _('Show known vaccines.'))
456 self.Bind(wx.EVT_MENU, self.__on_manage_vaccines, item)
457
458 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
459 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
460
461 item = menu_master_data.Append(-1, _('Immunizables'), _('Show conditions known to be preventable by vaccination.'))
462 self.Bind(wx.EVT_MENU, self.__on_manage_vaccination_indications, item)
463
464
465 menu_users = wx.Menu()
466 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
467
468 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
469 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
470
471 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
472 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
473
474
475 menu_gnumed.AppendSeparator()
476
477 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
478 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
479
480 self.mainmenu.Append(menu_gnumed, '&GNUmed')
481
482
483 menu_patient = wx.Menu()
484
485 ID_CREATE_PATIENT = wx.NewId()
486 menu_patient.Append(ID_CREATE_PATIENT, _('Register person'), _("Register a new person with GNUmed"))
487 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
488
489
490
491
492 ID_LOAD_EXT_PAT = wx.NewId()
493 menu_patient.Append(ID_LOAD_EXT_PAT, _('Load external'), _('Load and possibly create person from an external source.'))
494 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
495
496 ID_DEL_PAT = wx.NewId()
497 menu_patient.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
498 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
499
500 item = menu_patient.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
501 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
502
503 menu_patient.AppendSeparator()
504
505 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
506 menu_patient.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
507 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
508
509
510 ID = wx.NewId()
511 menu_patient.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
512 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
513
514 menu_patient.AppendSeparator()
515
516 self.mainmenu.Append(menu_patient, '&Person')
517 self.__gb['main.patientmenu'] = menu_patient
518
519
520 menu_emr = wx.Menu()
521 self.mainmenu.Append(menu_emr, _("&EMR"))
522 self.__gb['main.emrmenu'] = menu_emr
523
524
525 menu_emr_show = wx.Menu()
526 menu_emr.AppendMenu(wx.NewId(), _('Show as ...'), menu_emr_show)
527 self.__gb['main.emr_showmenu'] = menu_emr_show
528
529
530 item = menu_emr_show.Append(-1, _('Summary'), _('Show a high-level summary of the EMR.'))
531 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
532
533
534 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
535 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
536
537 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
538 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
539
540
541 menu_emr_edit = wx.Menu()
542 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
543
544 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'))
545 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
546
547 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
548 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
549
550 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
551 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
552
553 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
554 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
555
556 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
557 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
558
559 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
560 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
561
562 item = menu_emr_edit.Append(-1, _('&Measurement(s)'), _('Add (a) measurement result(s) for the current patient.'))
563 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
564
565 item = menu_emr_edit.Append(-1, _('&Vaccination(s)'), _('Add (a) vaccination(s) for the current patient.'))
566 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
567
568
569
570
571
572
573
574 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
575 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
576
577
578 item = menu_emr.Append(-1, _('&Encounters list'), _('List all encounters including empty ones.'))
579 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
580
581
582 menu_emr.AppendSeparator()
583
584 menu_emr_export = wx.Menu()
585 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
586
587 ID_EXPORT_EMR_ASCII = wx.NewId()
588 menu_emr_export.Append (
589 ID_EXPORT_EMR_ASCII,
590 _('Text document'),
591 _("Export the EMR of the active patient into a text file")
592 )
593 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
594
595 ID_EXPORT_EMR_JOURNAL = wx.NewId()
596 menu_emr_export.Append (
597 ID_EXPORT_EMR_JOURNAL,
598 _('Journal'),
599 _("Export the EMR of the active patient as a chronological journal into a text file")
600 )
601 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
602
603 ID_EXPORT_MEDISTAR = wx.NewId()
604 menu_emr_export.Append (
605 ID_EXPORT_MEDISTAR,
606 _('MEDISTAR import format'),
607 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
608 )
609 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
610
611
612 menu_emr.AppendSeparator()
613
614
615 menu_paperwork = wx.Menu()
616
617 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
618 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
619
620 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
621
622
623 self.menu_tools = wx.Menu()
624 self.__gb['main.toolsmenu'] = self.menu_tools
625 self.mainmenu.Append(self.menu_tools, _("&Tools"))
626
627 ID_DICOM_VIEWER = wx.NewId()
628 viewer = _('no viewer installed')
629 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
630 viewer = u'OsiriX'
631 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
632 viewer = u'Aeskulap'
633 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
634 viewer = u'AMIDE'
635 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
636 viewer = u'(x)medcon'
637 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)
638 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
639 if viewer == _('no viewer installed'):
640 _log.info('neither of OsiriX / Aeskulap / AMIDE / xmedcon found, disabling "DICOM viewer" menu item')
641 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
642
643
644
645
646
647 ID = wx.NewId()
648 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
649 wx.EVT_MENU(self, ID, self.__on_snellen)
650
651 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
652 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
653
654 self.menu_tools.AppendSeparator()
655
656
657 menu_knowledge = wx.Menu()
658 self.__gb['main.knowledgemenu'] = menu_knowledge
659 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
660
661 menu_drug_dbs = wx.Menu()
662 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
663
664 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
665 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
666
667
668
669
670
671
672 menu_id = wx.NewId()
673 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
674 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
675
676
677
678
679 ID_MEDICAL_LINKS = wx.NewId()
680 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
681 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
682
683
684 self.menu_office = wx.Menu()
685
686 self.__gb['main.officemenu'] = self.menu_office
687 self.mainmenu.Append(self.menu_office, _('&Office'))
688
689 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
690 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
691
692 self.menu_office.AppendSeparator()
693
694
695 help_menu = wx.Menu()
696
697 ID = wx.NewId()
698 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
699 wx.EVT_MENU(self, ID, self.__on_display_wiki)
700
701 ID = wx.NewId()
702 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
703 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
704
705 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
706 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
707
708 menu_debugging = wx.Menu()
709 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
710
711 ID_SCREENSHOT = wx.NewId()
712 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
713 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
714
715 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
716 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
717
718 ID = wx.NewId()
719 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
720 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
721
722 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
723 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
724
725 ID = wx.NewId()
726 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
727 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
728
729 ID_UNBLOCK = wx.NewId()
730 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
731 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
732
733 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
734 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
735
736
737
738
739 if _cfg.get(option = 'debug'):
740 ID_TOGGLE_PAT_LOCK = wx.NewId()
741 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient'), _('Lock/unlock patient - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
742 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
743
744 ID_TEST_EXCEPTION = wx.NewId()
745 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
746 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
747
748 ID = wx.NewId()
749 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
750 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
751 try:
752 import wx.lib.inspection
753 except ImportError:
754 menu_debugging.Enable(id = ID, enable = False)
755
756 help_menu.AppendSeparator()
757
758 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
759 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
760
761 ID_CONTRIBUTORS = wx.NewId()
762 help_menu.Append(ID_CONTRIBUTORS, _('GNUmed contributors'), _('show GNUmed contributors'))
763 wx.EVT_MENU(self, ID_CONTRIBUTORS, self.__on_show_contributors)
764
765 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
766 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
767
768 help_menu.AppendSeparator()
769
770
771 self.__gb['main.helpmenu'] = help_menu
772 self.mainmenu.Append(help_menu, _("&Help"))
773
774
775
776 self.SetMenuBar(self.mainmenu)
777
780
781
782
784 """register events we want to react to"""
785
786 wx.EVT_CLOSE(self, self.OnClose)
787 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
788 wx.EVT_END_SESSION(self, self._on_end_session)
789
790 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
791 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
792 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
793 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
794 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
795 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
796 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
797 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
798
799 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
800
801 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
802
803 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
804
805 _log.debug('registering plugin with menu system')
806 _log.debug(' generic name: %s', plugin_name)
807 _log.debug(' class name: %s', class_name)
808 _log.debug(' specific menu: %s', menu_name)
809 _log.debug(' menu item: %s', menu_item_name)
810
811
812 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
813 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
814 self.menu_id2plugin[item.Id] = class_name
815
816
817 if menu_name is not None:
818 menu = self.__gb['main.%smenu' % menu_name]
819 item = menu.Append(-1, menu_item_name, menu_help_string)
820 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
821 self.menu_id2plugin[item.Id] = class_name
822
823 return True
824
826 gmDispatcher.send (
827 signal = u'display_widget',
828 name = self.menu_id2plugin[evt.Id]
829 )
830
832 wx.Bell()
833 wx.Bell()
834 wx.Bell()
835 _log.warning('unhandled event detected: QUERY_END_SESSION')
836 _log.info('we should be saving ourselves from here')
837 gmLog2.flush()
838 print "unhandled event detected: QUERY_END_SESSION"
839
841 wx.Bell()
842 wx.Bell()
843 wx.Bell()
844 _log.warning('unhandled event detected: END_SESSION')
845 gmLog2.flush()
846 print "unhandled event detected: END_SESSION"
847
849 if not callable(callback):
850 raise TypeError(u'callback [%s] not callable' % callback)
851
852 self.__pre_exit_callbacks.append(callback)
853
854 - def _on_set_statustext_pubsub(self, context=None):
855 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
856 wx.CallAfter(self.SetStatusText, msg)
857
858 try:
859 if context.data['beep']:
860 wx.Bell()
861 except KeyError:
862 pass
863
864 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
865
866 if msg is None:
867 msg = _('programmer forgot to specify status message')
868
869 if loglevel is not None:
870 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
871
872 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
873 wx.CallAfter(self.SetStatusText, msg)
874
875 if beep:
876 wx.Bell()
877
879 wx.CallAfter(self.__on_db_maintenance_warning)
880
882
883 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
884 wx.Bell()
885 if not wx.GetApp().IsActive():
886 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
887
888 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
889
890 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
891 None,
892 -1,
893 caption = _('Database shutdown warning'),
894 question = _(
895 'The database will be shut down for maintenance\n'
896 'in a few minutes.\n'
897 '\n'
898 'In order to not suffer any loss of data you\n'
899 'will need to save your current work and log\n'
900 'out of this GNUmed client.\n'
901 ),
902 button_defs = [
903 {
904 u'label': _('Close now'),
905 u'tooltip': _('Close this GNUmed client immediately.'),
906 u'default': False
907 },
908 {
909 u'label': _('Finish work'),
910 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
911 u'default': True
912 }
913 ]
914 )
915 decision = dlg.ShowModal()
916 if decision == wx.ID_YES:
917 top_win = wx.GetApp().GetTopWindow()
918 wx.CallAfter(top_win.Close)
919
921 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
922
924
925 if not wx.GetApp().IsActive():
926 if urgent:
927 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
928 else:
929 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
930
931 if msg is not None:
932 self.SetStatusText(msg)
933
934 if urgent:
935 wx.Bell()
936
937 gmHooks.run_hook_script(hook = u'request_user_attention')
938
940 wx.CallAfter(self.__on_pat_name_changed)
941
943 self.__update_window_title()
944
946 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
947
949 self.__update_window_title()
950 try:
951 gmHooks.run_hook_script(hook = u'post_patient_activation')
952 except:
953 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
954 raise
955
957 return self.__sanity_check_encounter()
958
1016
1017
1018
1021
1029
1032
1033
1034
1049
1072
1074 from Gnumed.wxpython import gmAbout
1075 contribs = gmAbout.cContributorsDlg (
1076 parent = self,
1077 id = -1,
1078 title = _('GNUmed contributors'),
1079 size = wx.Size(400,600),
1080 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1081 )
1082 contribs.ShowModal()
1083 del contribs
1084 del gmAbout
1085
1086
1087
1089 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1090 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1091 self.Close(True)
1092 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1093
1096
1098 send = gmGuiHelpers.gm_show_question (
1099 _('This will send a notification about database downtime\n'
1100 'to all GNUmed clients connected to your database.\n'
1101 '\n'
1102 'Do you want to send the notification ?\n'
1103 ),
1104 _('Announcing database maintenance downtime')
1105 )
1106 if not send:
1107 return
1108 gmPG2.send_maintenance_notification()
1109
1110
1113
1114
1115
1147
1160
1161 gmCfgWidgets.configure_string_option (
1162 message = _(
1163 'Some network installations cannot cope with loading\n'
1164 'documents of arbitrary size in one piece from the\n'
1165 'database (mainly observed on older Windows versions)\n.'
1166 '\n'
1167 'Under such circumstances documents need to be retrieved\n'
1168 'in chunks and reassembled on the client.\n'
1169 '\n'
1170 'Here you can set the size (in Bytes) above which\n'
1171 'GNUmed will retrieve documents in chunks. Setting this\n'
1172 'value to 0 will disable the chunking protocol.'
1173 ),
1174 option = 'horstspace.blob_export_chunk_size',
1175 bias = 'workplace',
1176 default_value = 1024 * 1024,
1177 validator = is_valid
1178 )
1179
1180
1181
1249
1253
1254
1255
1264
1265 gmCfgWidgets.configure_string_option (
1266 message = _(
1267 'When GNUmed cannot find an OpenOffice server it\n'
1268 'will try to start one. OpenOffice, however, needs\n'
1269 'some time to fully start up.\n'
1270 '\n'
1271 'Here you can set the time for GNUmed to wait for OOo.\n'
1272 ),
1273 option = 'external.ooo.startup_settle_time',
1274 bias = 'workplace',
1275 default_value = 2.0,
1276 validator = is_valid
1277 )
1278
1281
1293
1294 gmCfgWidgets.configure_string_option (
1295 message = _(
1296 'GNUmed will use this URL to access a website which lets\n'
1297 'you report an adverse drug reaction (ADR).\n'
1298 '\n'
1299 'You can leave this empty but to set it to a specific\n'
1300 'address the URL must be accessible now.'
1301 ),
1302 option = 'external.urls.report_ADR',
1303 bias = 'user',
1304 default_value = u'https://dcgma.org/uaw/meldung.php',
1305 validator = is_valid
1306 )
1307
1319
1320 gmCfgWidgets.configure_string_option (
1321 message = _(
1322 'GNUmed will use this URL to access a website which lets\n'
1323 'you report an adverse vaccination reaction (vADR).\n'
1324 '\n'
1325 'If you set it to a specific address that URL must be\n'
1326 'accessible now. If you leave it empty it will fall back\n'
1327 'to the URL for reporting other adverse drug reactions.'
1328 ),
1329 option = 'external.urls.report_vaccine_ADR',
1330 bias = 'user',
1331 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',
1332 validator = is_valid
1333 )
1334
1346
1347 gmCfgWidgets.configure_string_option (
1348 message = _(
1349 'GNUmed will use this URL to access an encyclopedia of\n'
1350 'measurement/lab methods from within the measurments grid.\n'
1351 '\n'
1352 'You can leave this empty but to set it to a specific\n'
1353 'address the URL must be accessible now.'
1354 ),
1355 option = 'external.urls.measurements_encyclopedia',
1356 bias = 'user',
1357 default_value = u'http://www.laborlexikon.de',
1358 validator = is_valid
1359 )
1360
1372
1373 gmCfgWidgets.configure_string_option (
1374 message = _(
1375 'GNUmed will use this URL to access a page showing\n'
1376 'vaccination schedules.\n'
1377 '\n'
1378 'You can leave this empty but to set it to a specific\n'
1379 'address the URL must be accessible now.'
1380 ),
1381 option = 'external.urls.vaccination_plans',
1382 bias = 'user',
1383 default_value = u'http://www.bundesaerztekammer.de/downloads/ImpfempfehlungenRKI2009.pdf',
1384 validator = is_valid
1385 )
1386
1399
1400 gmCfgWidgets.configure_string_option (
1401 message = _(
1402 'Enter the shell command with which to start the\n'
1403 'the ACS risk assessment calculator.\n'
1404 '\n'
1405 'GNUmed will try to verify the path which may,\n'
1406 'however, fail if you are using an emulator such\n'
1407 'as Wine. Nevertheless, starting the calculator\n'
1408 'will work as long as the shell command is correct\n'
1409 'despite the failing test.'
1410 ),
1411 option = 'external.tools.acs_risk_calculator_cmd',
1412 bias = 'user',
1413 validator = is_valid
1414 )
1415
1418
1431
1432 gmCfgWidgets.configure_string_option (
1433 message = _(
1434 'Enter the shell command with which to start\n'
1435 'the FreeDiams drug database frontend.\n'
1436 '\n'
1437 'GNUmed will try to verify that path.'
1438 ),
1439 option = 'external.tools.freediams_cmd',
1440 bias = 'workplace',
1441 default_value = None,
1442 validator = is_valid
1443 )
1444
1457
1458 gmCfgWidgets.configure_string_option (
1459 message = _(
1460 'Enter the shell command with which to start the\n'
1461 'the IFAP drug database.\n'
1462 '\n'
1463 'GNUmed will try to verify the path which may,\n'
1464 'however, fail if you are using an emulator such\n'
1465 'as Wine. Nevertheless, starting IFAP will work\n'
1466 'as long as the shell command is correct despite\n'
1467 'the failing test.'
1468 ),
1469 option = 'external.ifap-win.shell_command',
1470 bias = 'workplace',
1471 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1472 validator = is_valid
1473 )
1474
1475
1476
1525
1526
1527
1544
1547
1550
1555
1556 gmCfgWidgets.configure_string_option (
1557 message = _(
1558 'When a patient is activated GNUmed checks the\n'
1559 "proximity of the patient's birthday.\n"
1560 '\n'
1561 'If the birthday falls within the range of\n'
1562 ' "today %s <the interval you set here>"\n'
1563 'GNUmed will remind you of the recent or\n'
1564 'imminent anniversary.'
1565 ) % u'\u2213',
1566 option = u'patient_search.dob_warn_interval',
1567 bias = 'user',
1568 default_value = '1 week',
1569 validator = is_valid
1570 )
1571
1573
1574 gmCfgWidgets.configure_boolean_option (
1575 parent = self,
1576 question = _(
1577 'When adding progress notes do you want to\n'
1578 'allow opening several unassociated, new\n'
1579 'episodes for a patient at once ?\n'
1580 '\n'
1581 'This can be particularly helpful when entering\n'
1582 'progress notes on entirely new patients presenting\n'
1583 'with a multitude of problems on their first visit.'
1584 ),
1585 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1586 button_tooltips = [
1587 _('Yes, allow for multiple new episodes concurrently.'),
1588 _('No, only allow editing one new episode at a time.')
1589 ]
1590 )
1591
1637
1638
1639
1642
1656
1658 gmCfgWidgets.configure_boolean_option (
1659 parent = self,
1660 question = _(
1661 'Do you want GNUmed to show the encounter\n'
1662 'details editor when changing the active patient ?'
1663 ),
1664 option = 'encounter.show_editor_before_patient_change',
1665 button_tooltips = [
1666 _('Yes, show the encounter editor if it seems appropriate.'),
1667 _('No, never show the encounter editor even if it would seem useful.')
1668 ]
1669 )
1670
1675
1676 gmCfgWidgets.configure_string_option (
1677 message = _(
1678 'When a patient is activated GNUmed checks the\n'
1679 'chart for encounters lacking any entries.\n'
1680 '\n'
1681 'Any such encounters older than what you set\n'
1682 'here will be removed from the medical record.\n'
1683 '\n'
1684 'To effectively disable removal of such encounters\n'
1685 'set this option to an improbable value.\n'
1686 ),
1687 option = 'encounter.ttl_if_empty',
1688 bias = 'user',
1689 default_value = '1 week',
1690 validator = is_valid
1691 )
1692
1697
1698 gmCfgWidgets.configure_string_option (
1699 message = _(
1700 'When a patient is activated GNUmed checks the\n'
1701 'age of the most recent encounter.\n'
1702 '\n'
1703 'If that encounter is younger than this age\n'
1704 'the existing encounter will be continued.\n'
1705 '\n'
1706 '(If it is really old a new encounter is\n'
1707 ' started, or else GNUmed will ask you.)\n'
1708 ),
1709 option = 'encounter.minimum_ttl',
1710 bias = 'user',
1711 default_value = '1 hour 30 minutes',
1712 validator = is_valid
1713 )
1714
1719
1720 gmCfgWidgets.configure_string_option (
1721 message = _(
1722 'When a patient is activated GNUmed checks the\n'
1723 'age of the most recent encounter.\n'
1724 '\n'
1725 'If that encounter is older than this age\n'
1726 'GNUmed will always start a new encounter.\n'
1727 '\n'
1728 '(If it is very recent the existing encounter\n'
1729 ' is continued, or else GNUmed will ask you.)\n'
1730 ),
1731 option = 'encounter.maximum_ttl',
1732 bias = 'user',
1733 default_value = '6 hours',
1734 validator = is_valid
1735 )
1736
1745
1746 gmCfgWidgets.configure_string_option (
1747 message = _(
1748 'At any time there can only be one open (ongoing)\n'
1749 'episode for each health issue.\n'
1750 '\n'
1751 'When you try to open (add data to) an episode on a health\n'
1752 'issue GNUmed will check for an existing open episode on\n'
1753 'that issue. If there is any it will check the age of that\n'
1754 'episode. The episode is closed if it has been dormant (no\n'
1755 'data added, that is) for the period of time (in days) you\n'
1756 'set here.\n'
1757 '\n'
1758 "If the existing episode hasn't been dormant long enough\n"
1759 'GNUmed will consult you what to do.\n'
1760 '\n'
1761 'Enter maximum episode dormancy in DAYS:'
1762 ),
1763 option = 'episode.ttl',
1764 bias = 'user',
1765 default_value = 60,
1766 validator = is_valid
1767 )
1768
1799
1802
1817
1842
1854
1855 gmCfgWidgets.configure_string_option (
1856 message = _(
1857 'GNUmed can check for new releases being available. To do\n'
1858 'so it needs to load version information from an URL.\n'
1859 '\n'
1860 'The default URL is:\n'
1861 '\n'
1862 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1863 '\n'
1864 'but you can configure any other URL locally. Note\n'
1865 'that you must enter the location as a valid URL.\n'
1866 'Depending on the URL the client will need online\n'
1867 'access when checking for updates.'
1868 ),
1869 option = u'horstspace.update.url',
1870 bias = u'workplace',
1871 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1872 validator = is_valid
1873 )
1874
1892
1909
1920
1921 gmCfgWidgets.configure_string_option (
1922 message = _(
1923 'GNUmed can show the document review dialog after\n'
1924 'calling the appropriate viewer for that document.\n'
1925 '\n'
1926 'Select the conditions under which you want\n'
1927 'GNUmed to do so:\n'
1928 '\n'
1929 ' 0: never display the review dialog\n'
1930 ' 1: always display the dialog\n'
1931 ' 2: only if there is no previous review by me\n'
1932 '\n'
1933 'Note that if a viewer is configured to not block\n'
1934 'GNUmed during document display the review dialog\n'
1935 'will actually appear in parallel to the viewer.'
1936 ),
1937 option = u'horstspace.document_viewer.review_after_display',
1938 bias = u'user',
1939 default_value = 2,
1940 validator = is_valid
1941 )
1942
1956
1958
1959 dbcfg = gmCfg.cCfgSQL()
1960 cmd = dbcfg.get2 (
1961 option = u'external.tools.acs_risk_calculator_cmd',
1962 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1963 bias = 'user'
1964 )
1965
1966 if cmd is None:
1967 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
1968 return
1969
1970 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1971 try:
1972 subprocess.check_call (
1973 args = (cmd,),
1974 close_fds = True,
1975 cwd = cwd
1976 )
1977 except (OSError, ValueError, subprocess.CalledProcessError):
1978 _log.exception('there was a problem executing [%s]', cmd)
1979 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
1980 return
1981
1982 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
1983 for pdf in pdfs:
1984 try:
1985 open(pdf).close()
1986 except:
1987 _log.exception('error accessing [%s]', pdf)
1988 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the ARRIBA result in [%s] !') % pdf, beep = True)
1989 continue
1990
1991 doc = gmDocumentWidgets.save_file_as_new_document (
1992 parent = self,
1993 filename = pdf,
1994 document_type = u'risk assessment'
1995 )
1996
1997 try:
1998 os.remove(pdf)
1999 except StandardError:
2000 _log.exception('cannot remove [%s]', pdf)
2001
2002 if doc is None:
2003 continue
2004 doc['comment'] = u'ARRIBA: %s' % _('cardiovascular risk assessment')
2005 doc.save()
2006
2007 return
2008
2010 dlg = gmSnellen.cSnellenCfgDlg()
2011 if dlg.ShowModal() != wx.ID_OK:
2012 return
2013
2014 frame = gmSnellen.cSnellenChart (
2015 width = dlg.vals[0],
2016 height = dlg.vals[1],
2017 alpha = dlg.vals[2],
2018 mirr = dlg.vals[3],
2019 parent = None
2020 )
2021 frame.CentreOnScreen(wx.BOTH)
2022
2023
2024 frame.Show(True)
2025
2026
2028 webbrowser.open (
2029 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
2030 new = False,
2031 autoraise = True
2032 )
2033
2036
2038 webbrowser.open (
2039 url = 'http://www.kompendium.ch',
2040 new = False,
2041 autoraise = True
2042 )
2043
2044
2045
2049
2050
2051
2053 wx.CallAfter(self.__save_screenshot)
2054 evt.Skip()
2055
2057
2058 time.sleep(0.5)
2059
2060 rect = self.GetRect()
2061
2062
2063 if sys.platform == 'linux2':
2064 client_x, client_y = self.ClientToScreen((0, 0))
2065 border_width = client_x - rect.x
2066 title_bar_height = client_y - rect.y
2067
2068 if self.GetMenuBar():
2069 title_bar_height /= 2
2070 rect.width += (border_width * 2)
2071 rect.height += title_bar_height + border_width
2072
2073 wdc = wx.ScreenDC()
2074 mdc = wx.MemoryDC()
2075 img = wx.EmptyBitmap(rect.width, rect.height)
2076 mdc.SelectObject(img)
2077 mdc.Blit (
2078 0, 0,
2079 rect.width, rect.height,
2080 wdc,
2081 rect.x, rect.y
2082 )
2083
2084
2085 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2086 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2087 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2088
2090
2091 raise ValueError('raised ValueError to test exception handling')
2092
2094 import wx.lib.inspection
2095 wx.lib.inspection.InspectionTool().Show()
2096
2098 webbrowser.open (
2099 url = 'https://bugs.launchpad.net/gnumed/',
2100 new = False,
2101 autoraise = True
2102 )
2103
2105 webbrowser.open (
2106 url = 'http://wiki.gnumed.de',
2107 new = False,
2108 autoraise = True
2109 )
2110
2112 webbrowser.open (
2113 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
2114 new = False,
2115 autoraise = True
2116 )
2117
2119 webbrowser.open (
2120 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
2121 new = False,
2122 autoraise = True
2123 )
2124
2131
2135
2138
2145
2150
2152 name = os.path.basename(gmLog2._logfile_name)
2153 name, ext = os.path.splitext(name)
2154 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2155 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2156
2157 dlg = wx.FileDialog (
2158 parent = self,
2159 message = _("Save current log as..."),
2160 defaultDir = new_path,
2161 defaultFile = new_name,
2162 wildcard = "%s (*.log)|*.log" % _("log files"),
2163 style = wx.SAVE
2164 )
2165 choice = dlg.ShowModal()
2166 new_name = dlg.GetPath()
2167 dlg.Destroy()
2168 if choice != wx.ID_OK:
2169 return True
2170
2171 _log.warning('syncing log file for backup to [%s]', new_name)
2172 gmLog2.flush()
2173 shutil.copy2(gmLog2._logfile_name, new_name)
2174 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2175
2178
2179
2180
2182 """This is the wx.EVT_CLOSE handler.
2183
2184 - framework still functional
2185 """
2186 _log.debug('gmTopLevelFrame.OnClose() start')
2187 self._clean_exit()
2188 self.Destroy()
2189 _log.debug('gmTopLevelFrame.OnClose() end')
2190 return True
2191
2197
2202
2210
2217
2224
2234
2242
2250
2258
2266
2275
2283
2285 pat = gmPerson.gmCurrentPatient()
2286 if not pat.connected:
2287 gmDispatcher.send(signal = 'statustext', msg = _('Cannot show EMR summary. No active patient.'))
2288 return False
2289
2290 emr = pat.get_emr()
2291 dlg = wx.MessageDialog (
2292 parent = self,
2293 message = emr.format_statistics(),
2294 caption = _('EMR Summary'),
2295 style = wx.OK | wx.STAY_ON_TOP
2296 )
2297 dlg.ShowModal()
2298 dlg.Destroy()
2299 return True
2300
2303
2306
2308
2309 pat = gmPerson.gmCurrentPatient()
2310 if not pat.connected:
2311 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2312 return False
2313
2314 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2315
2316 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2317 gmTools.mkdir(aDefDir)
2318
2319 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2320 dlg = wx.FileDialog (
2321 parent = self,
2322 message = _("Save patient's EMR journal as..."),
2323 defaultDir = aDefDir,
2324 defaultFile = fname,
2325 wildcard = aWildcard,
2326 style = wx.SAVE
2327 )
2328 choice = dlg.ShowModal()
2329 fname = dlg.GetPath()
2330 dlg.Destroy()
2331 if choice != wx.ID_OK:
2332 return True
2333
2334 _log.debug('exporting EMR journal to [%s]' % fname)
2335
2336 exporter = gmPatientExporter.cEMRJournalExporter()
2337
2338 wx.BeginBusyCursor()
2339 try:
2340 fname = exporter.export_to_file(filename = fname)
2341 except:
2342 wx.EndBusyCursor()
2343 gmGuiHelpers.gm_show_error (
2344 _('Error exporting patient EMR as chronological journal.'),
2345 _('EMR journal export')
2346 )
2347 raise
2348 wx.EndBusyCursor()
2349
2350 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2351
2352 return True
2353
2360
2370
2372 curr_pat = gmPerson.gmCurrentPatient()
2373 if not curr_pat.connected:
2374 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2375 return False
2376
2377 enc = 'cp850'
2378 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2379 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2380 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2381
2384
2385
2386
2387
2388
2389
2390
2398
2406
2409
2416
2420
2424
2427
2430
2433
2436
2439
2442
2445
2448
2451
2454
2457
2460
2463
2468
2470 """Cleanup helper.
2471
2472 - should ALWAYS be called when this program is
2473 to be terminated
2474 - ANY code that should be executed before a
2475 regular shutdown should go in here
2476 - framework still functional
2477 """
2478 _log.debug('gmTopLevelFrame._clean_exit() start')
2479
2480
2481 listener = gmBackendListener.gmBackendListener()
2482 try:
2483 listener.shutdown()
2484 except:
2485 _log.exception('cannot stop backend notifications listener thread')
2486
2487
2488 if _scripting_listener is not None:
2489 try:
2490 _scripting_listener.shutdown()
2491 except:
2492 _log.exception('cannot stop scripting listener thread')
2493
2494
2495 self.clock_update_timer.Stop()
2496 gmTimer.shutdown()
2497 gmPhraseWheel.shutdown()
2498
2499
2500 for call_back in self.__pre_exit_callbacks:
2501 try:
2502 call_back()
2503 except:
2504 print "*** pre-exit callback failed ***"
2505 print call_back
2506 _log.exception('callback [%s] failed', call_back)
2507
2508
2509 gmDispatcher.send(u'application_closing')
2510
2511
2512 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2513
2514
2515 curr_width, curr_height = self.GetClientSizeTuple()
2516 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2517 dbcfg = gmCfg.cCfgSQL()
2518 dbcfg.set (
2519 option = 'main.window.width',
2520 value = curr_width,
2521 workplace = gmSurgery.gmCurrentPractice().active_workplace
2522 )
2523 dbcfg.set (
2524 option = 'main.window.height',
2525 value = curr_height,
2526 workplace = gmSurgery.gmCurrentPractice().active_workplace
2527 )
2528
2529 if _cfg.get(option = 'debug'):
2530 print '---=== GNUmed shutdown ===---'
2531 try:
2532 print _('You have to manually close this window to finalize shutting down GNUmed.')
2533 print _('This is so that you can inspect the console output at your leisure.')
2534 except UnicodeEncodeError:
2535 print 'You have to manually close this window to finalize shutting down GNUmed.'
2536 print 'This is so that you can inspect the console output at your leisure.'
2537 print '---=== GNUmed shutdown ===---'
2538
2539
2540 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2541
2542
2543 import threading
2544 _log.debug("%s active threads", threading.activeCount())
2545 for t in threading.enumerate():
2546 _log.debug('thread %s', t)
2547
2548 _log.debug('gmTopLevelFrame._clean_exit() end')
2549
2550
2551
2553
2554 if _cfg.get(option = 'slave'):
2555 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2556 _cfg.get(option = 'slave personality'),
2557 _cfg.get(option = 'xml-rpc port')
2558 )
2559 else:
2560 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2561
2563 """Update title of main window based on template.
2564
2565 This gives nice tooltips on iconified GNUmed instances.
2566
2567 User research indicates that in the title bar people want
2568 the date of birth, not the age, so please stick to this
2569 convention.
2570 """
2571 args = {}
2572
2573 pat = gmPerson.gmCurrentPatient()
2574 if pat.connected:
2575
2576
2577
2578
2579
2580
2581 args['pat'] = u'%s %s %s (%s) #%d' % (
2582 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2583
2584 pat['firstnames'],
2585 pat['lastnames'],
2586 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2587 pat['pk_identity']
2588 )
2589 else:
2590 args['pat'] = _('no patient')
2591
2592 args['prov'] = u'%s%s.%s' % (
2593 gmTools.coalesce(_provider['title'], u'', u'%s '),
2594 _provider['firstnames'][:1],
2595 _provider['lastnames']
2596 )
2597
2598 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2599
2600 self.SetTitle(self.__title_template % args)
2601
2602
2604 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2605 sb.SetStatusWidths([-1, 225])
2606
2607 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2608 self._cb_update_clock()
2609
2610 self.clock_update_timer.Start(milliseconds = 1000)
2611
2613 """Displays date and local time in the second slot of the status bar"""
2614 t = time.localtime(time.time())
2615 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2616 self.SetStatusText(st,1)
2617
2619 """Lock GNUmed client against unauthorized access"""
2620
2621
2622
2623 return
2624
2626 """Unlock the main notebook widgets
2627 As long as we are not logged into the database backend,
2628 all pages but the 'login' page of the main notebook widget
2629 are locked; i.e. not accessible by the user
2630 """
2631
2632
2633
2634
2635
2636 return
2637
2639 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2640
2642
2644
2645 self.__starting_up = True
2646
2647 gmExceptionHandlingWidgets.install_wx_exception_handler()
2648 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2649
2650
2651
2652
2653 self.SetAppName(u'gnumed')
2654 self.SetVendorName(u'The GNUmed Development Community.')
2655 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2656 paths.init_paths(wx = wx, app_name = u'gnumed')
2657
2658 if not self.__setup_prefs_file():
2659 return False
2660
2661 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2662
2663 self.__guibroker = gmGuiBroker.GuiBroker()
2664 self.__setup_platform()
2665
2666 if not self.__establish_backend_connection():
2667 return False
2668
2669 if not _cfg.get(option = 'skip-update-check'):
2670 self.__check_for_updates()
2671
2672 if _cfg.get(option = 'slave'):
2673 if not self.__setup_scripting_listener():
2674 return False
2675
2676
2677 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640,440))
2678 frame.CentreOnScreen(wx.BOTH)
2679 self.SetTopWindow(frame)
2680 frame.Show(True)
2681
2682 if _cfg.get(option = 'debug'):
2683 self.RedirectStdio()
2684 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2685
2686
2687 print '---=== GNUmed startup ===---'
2688 print _('redirecting STDOUT/STDERR to this log window')
2689 print '---=== GNUmed startup ===---'
2690
2691 self.__setup_user_activity_timer()
2692 self.__register_events()
2693
2694 wx.CallAfter(self._do_after_init)
2695
2696 return True
2697
2699 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2700
2701 - after destroying all application windows and controls
2702 - before wx.Windows internal cleanup
2703 """
2704 _log.debug('gmApp.OnExit() start')
2705
2706 self.__shutdown_user_activity_timer()
2707
2708 if _cfg.get(option = 'debug'):
2709 self.RestoreStdio()
2710 sys.stdin = sys.__stdin__
2711 sys.stdout = sys.__stdout__
2712 sys.stderr = sys.__stderr__
2713
2714 _log.debug('gmApp.OnExit() end')
2715
2717 wx.Bell()
2718 wx.Bell()
2719 wx.Bell()
2720 _log.warning('unhandled event detected: QUERY_END_SESSION')
2721 _log.info('we should be saving ourselves from here')
2722 gmLog2.flush()
2723 print "unhandled event detected: QUERY_END_SESSION"
2724
2726 wx.Bell()
2727 wx.Bell()
2728 wx.Bell()
2729 _log.warning('unhandled event detected: END_SESSION')
2730 gmLog2.flush()
2731 print "unhandled event detected: END_SESSION"
2732
2743
2745 self.user_activity_detected = True
2746 evt.Skip()
2747
2749
2750 if self.user_activity_detected:
2751 self.elapsed_inactivity_slices = 0
2752 self.user_activity_detected = False
2753 self.elapsed_inactivity_slices += 1
2754 else:
2755 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2756
2757 pass
2758
2759 self.user_activity_timer.Start(oneShot = True)
2760
2761
2762
2764 try:
2765 kwargs['originated_in_database']
2766 print '==> got notification from database "%s":' % kwargs['signal']
2767 except KeyError:
2768 print '==> received signal from client: "%s"' % kwargs['signal']
2769
2770 del kwargs['signal']
2771 for key in kwargs.keys():
2772 print ' [%s]: %s' % (key, kwargs[key])
2773
2775 print "wx.lib.pubsub message:"
2776 print msg.topic
2777 print msg.data
2778
2784
2786 self.user_activity_detected = True
2787 self.elapsed_inactivity_slices = 0
2788
2789 self.max_user_inactivity_slices = 15
2790 self.user_activity_timer = gmTimer.cTimer (
2791 callback = self._on_user_activity_timer_expired,
2792 delay = 2000
2793 )
2794 self.user_activity_timer.Start(oneShot=True)
2795
2797 try:
2798 self.user_activity_timer.Stop()
2799 del self.user_activity_timer
2800 except:
2801 pass
2802
2804 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2805 wx.EVT_END_SESSION(self, self._on_end_session)
2806
2807
2808
2809
2810
2811 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2812
2813 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2814 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2840
2842 """Handle all the database related tasks necessary for startup."""
2843
2844
2845 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
2846
2847 from Gnumed.wxpython import gmAuthWidgets
2848 connected = gmAuthWidgets.connect_to_database (
2849 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
2850 require_version = not override
2851 )
2852 if not connected:
2853 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
2854 return False
2855
2856
2857 try:
2858 global _provider
2859 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
2860 except ValueError:
2861 account = gmPG2.get_current_user()
2862 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
2863 msg = _(
2864 'The database account [%s] cannot be used as a\n'
2865 'staff member login for GNUmed. There was an\n'
2866 'error retrieving staff details for it.\n\n'
2867 'Please ask your administrator for help.\n'
2868 ) % account
2869 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
2870 return False
2871
2872
2873 tmp = '%s%s %s (%s = %s)' % (
2874 gmTools.coalesce(_provider['title'], ''),
2875 _provider['firstnames'],
2876 _provider['lastnames'],
2877 _provider['short_alias'],
2878 _provider['db_user']
2879 )
2880 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
2881
2882
2883 surgery = gmSurgery.gmCurrentPractice()
2884 msg = surgery.db_logon_banner
2885 if msg.strip() != u'':
2886
2887 login = gmPG2.get_default_login()
2888 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
2889 login.database,
2890 gmTools.coalesce(login.host, u'localhost')
2891 ))
2892 msg = auth + msg + u'\n\n'
2893
2894 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
2895 None,
2896 -1,
2897 caption = _('Verifying database'),
2898 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
2899 button_defs = [
2900 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
2901 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
2902 ]
2903 )
2904 go_on = dlg.ShowModal()
2905 dlg.Destroy()
2906 if go_on != wx.ID_YES:
2907 _log.info('user decided to not connect to this database')
2908 return False
2909
2910
2911 self.__check_db_lang()
2912
2913 return True
2914
2916 """Setup access to a config file for storing preferences."""
2917
2918 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2919
2920 candidates = []
2921 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
2922 if explicit_file is not None:
2923 candidates.append(explicit_file)
2924
2925 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
2926 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
2927 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
2928
2929 prefs_file = None
2930 for candidate in candidates:
2931 try:
2932 open(candidate, 'a+').close()
2933 prefs_file = candidate
2934 break
2935 except IOError:
2936 continue
2937
2938 if prefs_file is None:
2939 msg = _(
2940 'Cannot find configuration file in any of:\n'
2941 '\n'
2942 ' %s\n'
2943 'You may need to use the comand line option\n'
2944 '\n'
2945 ' --conf-file=<FILE>'
2946 ) % '\n '.join(candidates)
2947 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
2948 return False
2949
2950 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
2951 _log.info('user preferences file: %s', prefs_file)
2952
2953 return True
2954
2956
2957 from socket import error as SocketError
2958 from Gnumed.pycommon import gmScriptingListener
2959 from Gnumed.wxpython import gmMacro
2960
2961 slave_personality = gmTools.coalesce (
2962 _cfg.get (
2963 group = u'workplace',
2964 option = u'slave personality',
2965 source_order = [
2966 ('explicit', 'return'),
2967 ('workbase', 'return'),
2968 ('user', 'return'),
2969 ('system', 'return')
2970 ]
2971 ),
2972 u'gnumed-client'
2973 )
2974 _cfg.set_option(option = 'slave personality', value = slave_personality)
2975
2976
2977 port = int (
2978 gmTools.coalesce (
2979 _cfg.get (
2980 group = u'workplace',
2981 option = u'xml-rpc port',
2982 source_order = [
2983 ('explicit', 'return'),
2984 ('workbase', 'return'),
2985 ('user', 'return'),
2986 ('system', 'return')
2987 ]
2988 ),
2989 9999
2990 )
2991 )
2992 _cfg.set_option(option = 'xml-rpc port', value = port)
2993
2994 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
2995 global _scripting_listener
2996 try:
2997 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
2998 except SocketError, e:
2999 _log.exception('cannot start GNUmed XML-RPC server')
3000 gmGuiHelpers.gm_show_error (
3001 aMessage = (
3002 'Cannot start the GNUmed server:\n'
3003 '\n'
3004 ' [%s]'
3005 ) % e,
3006 aTitle = _('GNUmed startup')
3007 )
3008 return False
3009
3010 return True
3011
3032
3034 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3035 _log.warning("system locale is undefined (probably meaning 'C')")
3036 return True
3037
3038
3039 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3040 db_lang = rows[0]['lang']
3041
3042 if db_lang is None:
3043 _log.debug("database locale currently not set")
3044 msg = _(
3045 "There is no language selected in the database for user [%s].\n"
3046 "Your system language is currently set to [%s].\n\n"
3047 "Do you want to set the database language to '%s' ?\n\n"
3048 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3049 checkbox_msg = _('Remember to ignore missing language')
3050 else:
3051 _log.debug("current database locale: [%s]" % db_lang)
3052 msg = _(
3053 "The currently selected database language ('%s') does\n"
3054 "not match the current system language ('%s').\n"
3055 "\n"
3056 "Do you want to set the database language to '%s' ?\n"
3057 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3058 checkbox_msg = _('Remember to ignore language mismatch')
3059
3060
3061 if db_lang == gmI18N.system_locale_level['full']:
3062 _log.debug('Database locale (%s) up to date.' % db_lang)
3063 return True
3064 if db_lang == gmI18N.system_locale_level['country']:
3065 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3066 return True
3067 if db_lang == gmI18N.system_locale_level['language']:
3068 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3069 return True
3070
3071 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3072
3073
3074 ignored_sys_lang = _cfg.get (
3075 group = u'backend',
3076 option = u'ignored mismatching system locale',
3077 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3078 )
3079
3080
3081 if gmI18N.system_locale == ignored_sys_lang:
3082 _log.info('configured to ignore system-to-database locale mismatch')
3083 return True
3084
3085
3086 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3087 None,
3088 -1,
3089 caption = _('Checking database language settings'),
3090 question = msg,
3091 button_defs = [
3092 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3093 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3094 ],
3095 show_checkbox = True,
3096 checkbox_msg = checkbox_msg,
3097 checkbox_tooltip = _(
3098 'Checking this will make GNUmed remember your decision\n'
3099 'until the system language is changed.\n'
3100 '\n'
3101 'You can also reactivate this inquiry by removing the\n'
3102 'corresponding "ignore" option from the configuration file\n'
3103 '\n'
3104 ' [%s]'
3105 ) % _cfg.get(option = 'user_preferences_file')
3106 )
3107 decision = dlg.ShowModal()
3108 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3109 dlg.Destroy()
3110
3111 if decision == wx.ID_NO:
3112 if not remember_ignoring_problem:
3113 return True
3114 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3115 gmCfg2.set_option_in_INI_file (
3116 filename = _cfg.get(option = 'user_preferences_file'),
3117 group = 'backend',
3118 option = 'ignored mismatching system locale',
3119 value = gmI18N.system_locale
3120 )
3121 return True
3122
3123
3124 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3125 if len(lang) > 0:
3126
3127
3128 rows, idx = gmPG2.run_rw_queries (
3129 link_obj = None,
3130 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3131 return_data = True
3132 )
3133 if rows[0][0]:
3134 _log.debug("Successfully set database language to [%s]." % lang)
3135 else:
3136 _log.error('Cannot set database language to [%s].' % lang)
3137 continue
3138 return True
3139
3140
3141 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3142 gmPG2.run_rw_queries(queries = [{
3143 'cmd': u'select i18n.force_curr_lang(%s)',
3144 'args': [gmI18N.system_locale_level['country']]
3145 }])
3146
3147 return True
3148
3150 try:
3151 kwargs['originated_in_database']
3152 print '==> got notification from database "%s":' % kwargs['signal']
3153 except KeyError:
3154 print '==> received signal from client: "%s"' % kwargs['signal']
3155
3156 del kwargs['signal']
3157 for key in kwargs.keys():
3158
3159 try: print ' [%s]: %s' % (key, kwargs[key])
3160 except: print 'cannot print signal information'
3161
3163
3164 try:
3165 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3166 print ' data: %s' % msg.data
3167 print msg
3168 except: print 'problem printing pubsub message information'
3169
3171
3172 if _cfg.get(option = 'debug'):
3173 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3174 _log.debug('gmDispatcher signal monitor activated')
3175 wx.lib.pubsub.Publisher().subscribe (
3176 listener = _signal_debugging_monitor_pubsub,
3177 topic = wx.lib.pubsub.getStrAllTopics()
3178 )
3179 _log.debug('wx.lib.pubsub signal monitor activated')
3180
3181
3182
3183
3184 app = gmApp(redirect = False, clearSigInt = False)
3185 app.MainLoop()
3186
3187
3188
3189 if __name__ == '__main__':
3190
3191 from GNUmed.pycommon import gmI18N
3192 gmI18N.activate_locale()
3193 gmI18N.install_domain()
3194
3195 _log.info('Starting up as main module.')
3196 main()
3197
3198
3199