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, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
355 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
356
357
358 menu_cfg_emr = wx.Menu()
359 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
360
361 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
362 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
363
364
365 menu_cfg_encounter = wx.Menu()
366 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
367
368 ID = wx.NewId()
369 menu_cfg_encounter.Append(ID, _('Edit on patient change'), _('Edit encounter details on changing of patients.'))
370 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
371
372 ID = wx.NewId()
373 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
374 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
375
376 ID = wx.NewId()
377 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
378 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
379
380 ID = wx.NewId()
381 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
382 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
383
384 ID = wx.NewId()
385 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
386 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
387
388
389 menu_cfg_episode = wx.Menu()
390 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
391
392 ID = wx.NewId()
393 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
394 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
395
396
397 menu_master_data = wx.Menu()
398 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
399
400 item = menu_master_data.Append(-1, _('Workplace profiles'), _('Manage the plugins to load per workplace.'))
401 self.Bind(wx.EVT_MENU, self.__on_configure_workplace, item)
402
403 menu_master_data.AppendSeparator()
404
405 item = menu_master_data.Append(-1, _('&Document types'), _('Manage the document types available in the system.'))
406 self.Bind(wx.EVT_MENU, self.__on_edit_doc_types, item)
407
408 item = menu_master_data.Append(-1, _('&Form templates'), _('Manage templates for forms and letters.'))
409 self.Bind(wx.EVT_MENU, self.__on_manage_form_templates, item)
410
411 item = menu_master_data.Append(-1, _('&Text expansions'), _('Manage keyword based text expansion macros.'))
412 self.Bind(wx.EVT_MENU, self.__on_manage_text_expansion, item)
413
414 menu_master_data.AppendSeparator()
415
416 item = menu_master_data.Append(-1, _('&Encounter types'), _('Manage encounter types.'))
417 self.Bind(wx.EVT_MENU, self.__on_manage_encounter_types, item)
418
419 item = menu_master_data.Append(-1, _('&Provinces'), _('Manage provinces (counties, territories, ...).'))
420 self.Bind(wx.EVT_MENU, self.__on_manage_provinces, item)
421
422 menu_master_data.AppendSeparator()
423
424 item = menu_master_data.Append(-1, _('Substances'), _('Manage substances in use.'))
425 self.Bind(wx.EVT_MENU, self.__on_manage_substances, item)
426
427 item = menu_master_data.Append(-1, _('Drugs'), _('Manage branded drugs.'))
428 self.Bind(wx.EVT_MENU, self.__on_manage_branded_drugs, item)
429
430 item = menu_master_data.Append(-1, _('Drug components'), _('Manage components of branded drugs.'))
431 self.Bind(wx.EVT_MENU, self.__on_manage_substances_in_brands, item)
432
433 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
434 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
435
436 menu_master_data.AppendSeparator()
437
438 item = menu_master_data.Append(-1, _('Diagnostic orgs'), _('Manage diagnostic organisations (path labs etc).'))
439 self.Bind(wx.EVT_MENU, self.__on_manage_test_orgs, item)
440
441 item = menu_master_data.Append(-1, _('&Test types'), _('Manage test/measurement types.'))
442 self.Bind(wx.EVT_MENU, self.__on_manage_test_types, item)
443
444 item = menu_master_data.Append(-1, _('&Meta test types'), _('Show meta test/measurement types.'))
445 self.Bind(wx.EVT_MENU, self.__on_manage_meta_test_types, item)
446
447 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
448 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
449
450 menu_master_data.AppendSeparator()
451
452 item = menu_master_data.Append(-1, _('Vaccines'), _('Show known vaccines.'))
453 self.Bind(wx.EVT_MENU, self.__on_manage_vaccines, item)
454
455 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
456 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
457
458 item = menu_master_data.Append(-1, _('Immunizables'), _('Show conditions known to be preventable by vaccination.'))
459 self.Bind(wx.EVT_MENU, self.__on_manage_vaccination_indications, item)
460
461
462 menu_users = wx.Menu()
463 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
464
465 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
466 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
467
468 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
469 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
470
471
472 menu_gnumed.AppendSeparator()
473
474 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
475 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
476
477 self.mainmenu.Append(menu_gnumed, '&GNUmed')
478
479
480 menu_patient = wx.Menu()
481
482 ID_CREATE_PATIENT = wx.NewId()
483 menu_patient.Append(ID_CREATE_PATIENT, _('Register person'), _("Register a new person with GNUmed"))
484 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
485
486
487
488
489 ID_LOAD_EXT_PAT = wx.NewId()
490 menu_patient.Append(ID_LOAD_EXT_PAT, _('Load external'), _('Load and possibly create person from an external source.'))
491 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
492
493 ID_DEL_PAT = wx.NewId()
494 menu_patient.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
495 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
496
497 item = menu_patient.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
498 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
499
500 menu_patient.AppendSeparator()
501
502 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
503 menu_patient.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
504 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
505
506
507 ID = wx.NewId()
508 menu_patient.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
509 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
510
511 menu_patient.AppendSeparator()
512
513 self.mainmenu.Append(menu_patient, '&Person')
514 self.__gb['main.patientmenu'] = menu_patient
515
516
517 menu_emr = wx.Menu()
518 self.mainmenu.Append(menu_emr, _("&EMR"))
519 self.__gb['main.emrmenu'] = menu_emr
520
521
522 menu_emr_show = wx.Menu()
523 menu_emr.AppendMenu(wx.NewId(), _('Show as ...'), menu_emr_show)
524 self.__gb['main.emr_showmenu'] = menu_emr_show
525
526
527 item = menu_emr_show.Append(-1, _('Summary'), _('Show a high-level summary of the EMR.'))
528 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
529
530
531 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
532 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
533
534 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
535 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
536
537
538 menu_emr_edit = wx.Menu()
539 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
540
541 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'))
542 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
543
544 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
545 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
546
547 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
548 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
549
550 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
551 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
552
553 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
554 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
555
556 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
557 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
558
559 item = menu_emr_edit.Append(-1, _('&Measurement(s)'), _('Add (a) measurement result(s) for the current patient.'))
560 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
561
562 item = menu_emr_edit.Append(-1, _('&Vaccination(s)'), _('Add (a) vaccination(s) for the current patient.'))
563 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
564
565
566
567
568
569
570
571 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
572 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
573
574
575 item = menu_emr.Append(-1, _('&Encounters list'), _('List all encounters including empty ones.'))
576 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
577
578
579 menu_emr.AppendSeparator()
580
581 menu_emr_export = wx.Menu()
582 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
583
584 ID_EXPORT_EMR_ASCII = wx.NewId()
585 menu_emr_export.Append (
586 ID_EXPORT_EMR_ASCII,
587 _('Text document'),
588 _("Export the EMR of the active patient into a text file")
589 )
590 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
591
592 ID_EXPORT_EMR_JOURNAL = wx.NewId()
593 menu_emr_export.Append (
594 ID_EXPORT_EMR_JOURNAL,
595 _('Journal'),
596 _("Export the EMR of the active patient as a chronological journal into a text file")
597 )
598 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
599
600 ID_EXPORT_MEDISTAR = wx.NewId()
601 menu_emr_export.Append (
602 ID_EXPORT_MEDISTAR,
603 _('MEDISTAR import format'),
604 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
605 )
606 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
607
608
609 menu_emr.AppendSeparator()
610
611
612 menu_paperwork = wx.Menu()
613
614 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
615 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
616
617 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
618
619
620 self.menu_tools = wx.Menu()
621 self.__gb['main.toolsmenu'] = self.menu_tools
622 self.mainmenu.Append(self.menu_tools, _("&Tools"))
623
624 ID_DICOM_VIEWER = wx.NewId()
625 viewer = _('no viewer installed')
626 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
627 viewer = u'OsiriX'
628 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
629 viewer = u'Aeskulap'
630 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
631 viewer = u'AMIDE'
632 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
633 viewer = u'(x)medcon'
634 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)
635 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
636 if viewer == _('no viewer installed'):
637 _log.info('neither of OsiriX / Aeskulap / AMIDE / xmedcon found, disabling "DICOM viewer" menu item')
638 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
639
640
641
642
643
644 ID = wx.NewId()
645 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
646 wx.EVT_MENU(self, ID, self.__on_snellen)
647
648 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
649 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
650
651 self.menu_tools.AppendSeparator()
652
653
654 menu_knowledge = wx.Menu()
655 self.__gb['main.knowledgemenu'] = menu_knowledge
656 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
657
658 menu_drug_dbs = wx.Menu()
659 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
660
661 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
662 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
663
664
665
666
667
668
669 menu_id = wx.NewId()
670 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
671 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
672
673
674
675
676 ID_MEDICAL_LINKS = wx.NewId()
677 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
678 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
679
680
681 self.menu_office = wx.Menu()
682
683 self.__gb['main.officemenu'] = self.menu_office
684 self.mainmenu.Append(self.menu_office, _('&Office'))
685
686 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
687 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
688
689 self.menu_office.AppendSeparator()
690
691
692 help_menu = wx.Menu()
693
694 ID = wx.NewId()
695 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
696 wx.EVT_MENU(self, ID, self.__on_display_wiki)
697
698 ID = wx.NewId()
699 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
700 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
701
702 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
703 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
704
705 menu_debugging = wx.Menu()
706 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
707
708 ID_SCREENSHOT = wx.NewId()
709 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
710 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
711
712 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
713 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
714
715 ID = wx.NewId()
716 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
717 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
718
719 ID = wx.NewId()
720 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
721 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
722
723 ID_UNBLOCK = wx.NewId()
724 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
725 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
726
727 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
728 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
729
730
731
732
733 if _cfg.get(option = 'debug'):
734 ID_TOGGLE_PAT_LOCK = wx.NewId()
735 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient'), _('Lock/unlock patient - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
736 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
737
738 ID_TEST_EXCEPTION = wx.NewId()
739 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
740 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
741
742 ID = wx.NewId()
743 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
744 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
745 try:
746 import wx.lib.inspection
747 except ImportError:
748 menu_debugging.Enable(id = ID, enable = False)
749
750 help_menu.AppendSeparator()
751
752 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
753 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
754
755 ID_CONTRIBUTORS = wx.NewId()
756 help_menu.Append(ID_CONTRIBUTORS, _('GNUmed contributors'), _('show GNUmed contributors'))
757 wx.EVT_MENU(self, ID_CONTRIBUTORS, self.__on_show_contributors)
758
759 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
760 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
761
762 help_menu.AppendSeparator()
763
764
765 self.__gb['main.helpmenu'] = help_menu
766 self.mainmenu.Append(help_menu, _("&Help"))
767
768
769
770 self.SetMenuBar(self.mainmenu)
771
774
775
776
778 """register events we want to react to"""
779
780 wx.EVT_CLOSE(self, self.OnClose)
781 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
782 wx.EVT_END_SESSION(self, self._on_end_session)
783
784 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
785 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
786 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
787 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
788 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
789 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
790 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
791 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
792
793 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
794
795 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
796
797 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
798
799 _log.debug('registering plugin with menu system')
800 _log.debug(' generic name: %s', plugin_name)
801 _log.debug(' class name: %s', class_name)
802 _log.debug(' specific menu: %s', menu_name)
803 _log.debug(' menu item: %s', menu_item_name)
804
805
806 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
807 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
808 self.menu_id2plugin[item.Id] = class_name
809
810
811 if menu_name is not None:
812 menu = self.__gb['main.%smenu' % menu_name]
813 item = menu.Append(-1, menu_item_name, menu_help_string)
814 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
815 self.menu_id2plugin[item.Id] = class_name
816
817 return True
818
820 gmDispatcher.send (
821 signal = u'display_widget',
822 name = self.menu_id2plugin[evt.Id]
823 )
824
826 wx.Bell()
827 wx.Bell()
828 wx.Bell()
829 _log.warning('unhandled event detected: QUERY_END_SESSION')
830 _log.info('we should be saving ourselves from here')
831 gmLog2.flush()
832 print "unhandled event detected: QUERY_END_SESSION"
833
835 wx.Bell()
836 wx.Bell()
837 wx.Bell()
838 _log.warning('unhandled event detected: END_SESSION')
839 gmLog2.flush()
840 print "unhandled event detected: END_SESSION"
841
843 if not callable(callback):
844 raise TypeError(u'callback [%s] not callable' % callback)
845
846 self.__pre_exit_callbacks.append(callback)
847
848 - def _on_set_statustext_pubsub(self, context=None):
849 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
850 wx.CallAfter(self.SetStatusText, msg)
851
852 try:
853 if context.data['beep']:
854 wx.Bell()
855 except KeyError:
856 pass
857
858 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
859
860 if msg is None:
861 msg = _('programmer forgot to specify status message')
862
863 if loglevel is not None:
864 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
865
866 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
867 wx.CallAfter(self.SetStatusText, msg)
868
869 if beep:
870 wx.Bell()
871
873 wx.CallAfter(self.__on_db_maintenance_warning)
874
876
877 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
878 wx.Bell()
879 if not wx.GetApp().IsActive():
880 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
881
882 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
883
884 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
885 None,
886 -1,
887 caption = _('Database shutdown warning'),
888 question = _(
889 'The database will be shut down for maintenance\n'
890 'in a few minutes.\n'
891 '\n'
892 'In order to not suffer any loss of data you\n'
893 'will need to save your current work and log\n'
894 'out of this GNUmed client.\n'
895 ),
896 button_defs = [
897 {
898 u'label': _('Close now'),
899 u'tooltip': _('Close this GNUmed client immediately.'),
900 u'default': False
901 },
902 {
903 u'label': _('Finish work'),
904 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
905 u'default': True
906 }
907 ]
908 )
909 decision = dlg.ShowModal()
910 if decision == wx.ID_YES:
911 top_win = wx.GetApp().GetTopWindow()
912 wx.CallAfter(top_win.Close)
913
915 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
916
918
919 if not wx.GetApp().IsActive():
920 if urgent:
921 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
922 else:
923 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
924
925 if msg is not None:
926 self.SetStatusText(msg)
927
928 if urgent:
929 wx.Bell()
930
931 gmHooks.run_hook_script(hook = u'request_user_attention')
932
934 wx.CallAfter(self.__on_pat_name_changed)
935
937 self.__update_window_title()
938
940 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
941
943 self.__update_window_title()
944 try:
945 gmHooks.run_hook_script(hook = u'post_patient_activation')
946 except:
947 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
948 raise
949
951 return self.__sanity_check_encounter()
952
1010
1011
1012
1015
1023
1026
1027
1028
1043
1066
1068 from Gnumed.wxpython import gmAbout
1069 contribs = gmAbout.cContributorsDlg (
1070 parent = self,
1071 id = -1,
1072 title = _('GNUmed contributors'),
1073 size = wx.Size(400,600),
1074 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1075 )
1076 contribs.ShowModal()
1077 del contribs
1078 del gmAbout
1079
1080
1081
1083 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1084 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1085 self.Close(True)
1086 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1087
1090
1092 send = gmGuiHelpers.gm_show_question (
1093 _('This will send a notification about database downtime\n'
1094 'to all GNUmed clients connected to your database.\n'
1095 '\n'
1096 'Do you want to send the notification ?\n'
1097 ),
1098 _('Announcing database maintenance downtime')
1099 )
1100 if not send:
1101 return
1102 gmPG2.send_maintenance_notification()
1103
1104
1107
1108
1109
1141
1154
1155 gmCfgWidgets.configure_string_option (
1156 message = _(
1157 'Some network installations cannot cope with loading\n'
1158 'documents of arbitrary size in one piece from the\n'
1159 'database (mainly observed on older Windows versions)\n.'
1160 '\n'
1161 'Under such circumstances documents need to be retrieved\n'
1162 'in chunks and reassembled on the client.\n'
1163 '\n'
1164 'Here you can set the size (in Bytes) above which\n'
1165 'GNUmed will retrieve documents in chunks. Setting this\n'
1166 'value to 0 will disable the chunking protocol.'
1167 ),
1168 option = 'horstspace.blob_export_chunk_size',
1169 bias = 'workplace',
1170 default_value = 1024 * 1024,
1171 validator = is_valid
1172 )
1173
1174
1175
1243
1247
1248
1249
1258
1259 gmCfgWidgets.configure_string_option (
1260 message = _(
1261 'When GNUmed cannot find an OpenOffice server it\n'
1262 'will try to start one. OpenOffice, however, needs\n'
1263 'some time to fully start up.\n'
1264 '\n'
1265 'Here you can set the time for GNUmed to wait for OOo.\n'
1266 ),
1267 option = 'external.ooo.startup_settle_time',
1268 bias = 'workplace',
1269 default_value = 2.0,
1270 validator = is_valid
1271 )
1272
1275
1287
1288 gmCfgWidgets.configure_string_option (
1289 message = _(
1290 'GNUmed will use this URL to access a website which lets\n'
1291 'you report an adverse drug reaction (ADR).\n'
1292 '\n'
1293 'You can leave this empty but to set it to a specific\n'
1294 'address the URL must be accessible now.'
1295 ),
1296 option = 'external.urls.report_ADR',
1297 bias = 'user',
1298 default_value = u'https://dcgma.org/uaw/meldung.php',
1299 validator = is_valid
1300 )
1301
1313
1314 gmCfgWidgets.configure_string_option (
1315 message = _(
1316 'GNUmed will use this URL to access a website which lets\n'
1317 'you report an adverse vaccination reaction (vADR).\n'
1318 '\n'
1319 'If you set it to a specific address that URL must be\n'
1320 'accessible now. If you leave it empty it will fall back\n'
1321 'to the URL for reporting other adverse drug reactions.'
1322 ),
1323 option = 'external.urls.report_vaccine_ADR',
1324 bias = 'user',
1325 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',
1326 validator = is_valid
1327 )
1328
1340
1341 gmCfgWidgets.configure_string_option (
1342 message = _(
1343 'GNUmed will use this URL to access an encyclopedia of\n'
1344 'measurement/lab methods from within the measurments grid.\n'
1345 '\n'
1346 'You can leave this empty but to set it to a specific\n'
1347 'address the URL must be accessible now.'
1348 ),
1349 option = 'external.urls.measurements_encyclopedia',
1350 bias = 'user',
1351 default_value = u'http://www.laborlexikon.de',
1352 validator = is_valid
1353 )
1354
1367
1368 gmCfgWidgets.configure_string_option (
1369 message = _(
1370 'Enter the shell command with which to start the\n'
1371 'the ACS risk assessment calculator.\n'
1372 '\n'
1373 'GNUmed will try to verify the path which may,\n'
1374 'however, fail if you are using an emulator such\n'
1375 'as Wine. Nevertheless, starting the calculator\n'
1376 'will work as long as the shell command is correct\n'
1377 'despite the failing test.'
1378 ),
1379 option = 'external.tools.acs_risk_calculator_cmd',
1380 bias = 'user',
1381 validator = is_valid
1382 )
1383
1386
1399
1400 gmCfgWidgets.configure_string_option (
1401 message = _(
1402 'Enter the shell command with which to start\n'
1403 'the FreeDiams drug database frontend.\n'
1404 '\n'
1405 'GNUmed will try to verify that path.'
1406 ),
1407 option = 'external.tools.freediams_cmd',
1408 bias = 'workplace',
1409 default_value = None,
1410 validator = is_valid
1411 )
1412
1425
1426 gmCfgWidgets.configure_string_option (
1427 message = _(
1428 'Enter the shell command with which to start the\n'
1429 'the IFAP drug database.\n'
1430 '\n'
1431 'GNUmed will try to verify the path which may,\n'
1432 'however, fail if you are using an emulator such\n'
1433 'as Wine. Nevertheless, starting IFAP will work\n'
1434 'as long as the shell command is correct despite\n'
1435 'the failing test.'
1436 ),
1437 option = 'external.ifap-win.shell_command',
1438 bias = 'workplace',
1439 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1440 validator = is_valid
1441 )
1442
1443
1444
1493
1494
1495
1512
1515
1518
1523
1524 gmCfgWidgets.configure_string_option (
1525 message = _(
1526 'When a patient is activated GNUmed checks the\n'
1527 "proximity of the patient's birthday.\n"
1528 '\n'
1529 'If the birthday falls within the range of\n'
1530 ' "today %s <the interval you set here>"\n'
1531 'GNUmed will remind you of the recent or\n'
1532 'imminent anniversary.'
1533 ) % u'\u2213',
1534 option = u'patient_search.dob_warn_interval',
1535 bias = 'user',
1536 default_value = '1 week',
1537 validator = is_valid
1538 )
1539
1541
1542 gmCfgWidgets.configure_boolean_option (
1543 parent = self,
1544 question = _(
1545 'When adding progress notes do you want to\n'
1546 'allow opening several unassociated, new\n'
1547 'episodes for a patient at once ?\n'
1548 '\n'
1549 'This can be particularly helpful when entering\n'
1550 'progress notes on entirely new patients presenting\n'
1551 'with a multitude of problems on their first visit.'
1552 ),
1553 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1554 button_tooltips = [
1555 _('Yes, allow for multiple new episodes concurrently.'),
1556 _('No, only allow editing one new episode at a time.')
1557 ]
1558 )
1559
1605
1606
1607
1610
1624
1626 gmCfgWidgets.configure_boolean_option (
1627 parent = self,
1628 question = _(
1629 'Do you want GNUmed to show the encounter\n'
1630 'details editor when changing the active patient ?'
1631 ),
1632 option = 'encounter.show_editor_before_patient_change',
1633 button_tooltips = [
1634 _('Yes, show the encounter editor if it seems appropriate.'),
1635 _('No, never show the encounter editor even if it would seem useful.')
1636 ]
1637 )
1638
1643
1644 gmCfgWidgets.configure_string_option (
1645 message = _(
1646 'When a patient is activated GNUmed checks the\n'
1647 'chart for encounters lacking any entries.\n'
1648 '\n'
1649 'Any such encounters older than what you set\n'
1650 'here will be removed from the medical record.\n'
1651 '\n'
1652 'To effectively disable removal of such encounters\n'
1653 'set this option to an improbable value.\n'
1654 ),
1655 option = 'encounter.ttl_if_empty',
1656 bias = 'user',
1657 default_value = '1 week',
1658 validator = is_valid
1659 )
1660
1665
1666 gmCfgWidgets.configure_string_option (
1667 message = _(
1668 'When a patient is activated GNUmed checks the\n'
1669 'age of the most recent encounter.\n'
1670 '\n'
1671 'If that encounter is younger than this age\n'
1672 'the existing encounter will be continued.\n'
1673 '\n'
1674 '(If it is really old a new encounter is\n'
1675 ' started, or else GNUmed will ask you.)\n'
1676 ),
1677 option = 'encounter.minimum_ttl',
1678 bias = 'user',
1679 default_value = '1 hour 30 minutes',
1680 validator = is_valid
1681 )
1682
1687
1688 gmCfgWidgets.configure_string_option (
1689 message = _(
1690 'When a patient is activated GNUmed checks the\n'
1691 'age of the most recent encounter.\n'
1692 '\n'
1693 'If that encounter is older than this age\n'
1694 'GNUmed will always start a new encounter.\n'
1695 '\n'
1696 '(If it is very recent the existing encounter\n'
1697 ' is continued, or else GNUmed will ask you.)\n'
1698 ),
1699 option = 'encounter.maximum_ttl',
1700 bias = 'user',
1701 default_value = '6 hours',
1702 validator = is_valid
1703 )
1704
1713
1714 gmCfgWidgets.configure_string_option (
1715 message = _(
1716 'At any time there can only be one open (ongoing)\n'
1717 'episode for each health issue.\n'
1718 '\n'
1719 'When you try to open (add data to) an episode on a health\n'
1720 'issue GNUmed will check for an existing open episode on\n'
1721 'that issue. If there is any it will check the age of that\n'
1722 'episode. The episode is closed if it has been dormant (no\n'
1723 'data added, that is) for the period of time (in days) you\n'
1724 'set here.\n'
1725 '\n'
1726 "If the existing episode hasn't been dormant long enough\n"
1727 'GNUmed will consult you what to do.\n'
1728 '\n'
1729 'Enter maximum episode dormancy in DAYS:'
1730 ),
1731 option = 'episode.ttl',
1732 bias = 'user',
1733 default_value = 60,
1734 validator = is_valid
1735 )
1736
1767
1770
1785
1810
1822
1823 gmCfgWidgets.configure_string_option (
1824 message = _(
1825 'GNUmed can check for new releases being available. To do\n'
1826 'so it needs to load version information from an URL.\n'
1827 '\n'
1828 'The default URL is:\n'
1829 '\n'
1830 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1831 '\n'
1832 'but you can configure any other URL locally. Note\n'
1833 'that you must enter the location as a valid URL.\n'
1834 'Depending on the URL the client will need online\n'
1835 'access when checking for updates.'
1836 ),
1837 option = u'horstspace.update.url',
1838 bias = u'workplace',
1839 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1840 validator = is_valid
1841 )
1842
1860
1877
1888
1889 gmCfgWidgets.configure_string_option (
1890 message = _(
1891 'GNUmed can show the document review dialog after\n'
1892 'calling the appropriate viewer for that document.\n'
1893 '\n'
1894 'Select the conditions under which you want\n'
1895 'GNUmed to do so:\n'
1896 '\n'
1897 ' 0: never display the review dialog\n'
1898 ' 1: always display the dialog\n'
1899 ' 2: only if there is no previous review by me\n'
1900 '\n'
1901 'Note that if a viewer is configured to not block\n'
1902 'GNUmed during document display the review dialog\n'
1903 'will actually appear in parallel to the viewer.'
1904 ),
1905 option = u'horstspace.document_viewer.review_after_display',
1906 bias = u'user',
1907 default_value = 2,
1908 validator = is_valid
1909 )
1910
1924
1926
1927 dbcfg = gmCfg.cCfgSQL()
1928 cmd = dbcfg.get2 (
1929 option = u'external.tools.acs_risk_calculator_cmd',
1930 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1931 bias = 'user'
1932 )
1933
1934 if cmd is None:
1935 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
1936 return
1937
1938 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1939 try:
1940 subprocess.check_call (
1941 args = (cmd,),
1942 close_fds = True,
1943 cwd = cwd
1944 )
1945 except (OSError, ValueError, subprocess.CalledProcessError):
1946 _log.exception('there was a problem executing [%s]', cmd)
1947 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
1948 return
1949
1950 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
1951 for pdf in pdfs:
1952 try:
1953 open(pdf).close()
1954 except:
1955 _log.exception('error accessing [%s]', pdf)
1956 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the ARRIBA result in [%s] !') % pdf, beep = True)
1957 continue
1958
1959 doc = gmDocumentWidgets.save_file_as_new_document (
1960 parent = self,
1961 filename = pdf,
1962 document_type = u'risk assessment'
1963 )
1964
1965 try:
1966 os.remove(pdf)
1967 except StandardError:
1968 _log.exception('cannot remove [%s]', pdf)
1969
1970 if doc is None:
1971 continue
1972 doc['comment'] = u'ARRIBA: %s' % _('cardiovascular risk assessment')
1973 doc.save()
1974
1975 return
1976
1978 dlg = gmSnellen.cSnellenCfgDlg()
1979 if dlg.ShowModal() != wx.ID_OK:
1980 return
1981
1982 frame = gmSnellen.cSnellenChart (
1983 width = dlg.vals[0],
1984 height = dlg.vals[1],
1985 alpha = dlg.vals[2],
1986 mirr = dlg.vals[3],
1987 parent = None
1988 )
1989 frame.CentreOnScreen(wx.BOTH)
1990
1991
1992 frame.Show(True)
1993
1994
1996 webbrowser.open (
1997 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
1998 new = False,
1999 autoraise = True
2000 )
2001
2004
2006 webbrowser.open (
2007 url = 'http://www.kompendium.ch',
2008 new = False,
2009 autoraise = True
2010 )
2011
2012
2013
2017
2018
2019
2021 wx.CallAfter(self.__save_screenshot)
2022 evt.Skip()
2023
2025
2026 time.sleep(0.5)
2027
2028 rect = self.GetRect()
2029
2030
2031 if sys.platform == 'linux2':
2032 client_x, client_y = self.ClientToScreen((0, 0))
2033 border_width = client_x - rect.x
2034 title_bar_height = client_y - rect.y
2035
2036 if self.GetMenuBar():
2037 title_bar_height /= 2
2038 rect.width += (border_width * 2)
2039 rect.height += title_bar_height + border_width
2040
2041 wdc = wx.ScreenDC()
2042 mdc = wx.MemoryDC()
2043 img = wx.EmptyBitmap(rect.width, rect.height)
2044 mdc.SelectObject(img)
2045 mdc.Blit (
2046 0, 0,
2047 rect.width, rect.height,
2048 wdc,
2049 rect.x, rect.y
2050 )
2051
2052
2053 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2054 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2055 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2056
2058
2059 raise ValueError('raised ValueError to test exception handling')
2060
2062 import wx.lib.inspection
2063 wx.lib.inspection.InspectionTool().Show()
2064
2066 webbrowser.open (
2067 url = 'https://bugs.launchpad.net/gnumed/',
2068 new = False,
2069 autoraise = True
2070 )
2071
2073 webbrowser.open (
2074 url = 'http://wiki.gnumed.de',
2075 new = False,
2076 autoraise = True
2077 )
2078
2080 webbrowser.open (
2081 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
2082 new = False,
2083 autoraise = True
2084 )
2085
2087 webbrowser.open (
2088 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
2089 new = False,
2090 autoraise = True
2091 )
2092
2099
2103
2106
2113
2118
2120 name = os.path.basename(gmLog2._logfile_name)
2121 name, ext = os.path.splitext(name)
2122 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2123 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2124
2125 dlg = wx.FileDialog (
2126 parent = self,
2127 message = _("Save current log as..."),
2128 defaultDir = new_path,
2129 defaultFile = new_name,
2130 wildcard = "%s (*.log)|*.log" % _("log files"),
2131 style = wx.SAVE
2132 )
2133 choice = dlg.ShowModal()
2134 new_name = dlg.GetPath()
2135 dlg.Destroy()
2136 if choice != wx.ID_OK:
2137 return True
2138
2139 _log.warning('syncing log file for backup to [%s]', new_name)
2140 gmLog2.flush()
2141 shutil.copy2(gmLog2._logfile_name, new_name)
2142 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2143
2144
2145
2147 """This is the wx.EVT_CLOSE handler.
2148
2149 - framework still functional
2150 """
2151 _log.debug('gmTopLevelFrame.OnClose() start')
2152 self._clean_exit()
2153 self.Destroy()
2154 _log.debug('gmTopLevelFrame.OnClose() end')
2155 return True
2156
2162
2167
2175
2182
2189
2199
2207
2215
2223
2231
2240
2248
2250 pat = gmPerson.gmCurrentPatient()
2251 if not pat.connected:
2252 gmDispatcher.send(signal = 'statustext', msg = _('Cannot show EMR summary. No active patient.'))
2253 return False
2254
2255 emr = pat.get_emr()
2256 dlg = wx.MessageDialog (
2257 parent = self,
2258 message = emr.format_statistics(),
2259 caption = _('EMR Summary'),
2260 style = wx.OK | wx.STAY_ON_TOP
2261 )
2262 dlg.ShowModal()
2263 dlg.Destroy()
2264 return True
2265
2268
2271
2273
2274 pat = gmPerson.gmCurrentPatient()
2275 if not pat.connected:
2276 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2277 return False
2278
2279 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2280
2281 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2282 gmTools.mkdir(aDefDir)
2283
2284 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2285 dlg = wx.FileDialog (
2286 parent = self,
2287 message = _("Save patient's EMR journal as..."),
2288 defaultDir = aDefDir,
2289 defaultFile = fname,
2290 wildcard = aWildcard,
2291 style = wx.SAVE
2292 )
2293 choice = dlg.ShowModal()
2294 fname = dlg.GetPath()
2295 dlg.Destroy()
2296 if choice != wx.ID_OK:
2297 return True
2298
2299 _log.debug('exporting EMR journal to [%s]' % fname)
2300
2301 exporter = gmPatientExporter.cEMRJournalExporter()
2302
2303 wx.BeginBusyCursor()
2304 try:
2305 fname = exporter.export_to_file(filename = fname)
2306 except:
2307 wx.EndBusyCursor()
2308 gmGuiHelpers.gm_show_error (
2309 _('Error exporting patient EMR as chronological journal.'),
2310 _('EMR journal export')
2311 )
2312 raise
2313 wx.EndBusyCursor()
2314
2315 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2316
2317 return True
2318
2325
2335
2337 curr_pat = gmPerson.gmCurrentPatient()
2338 if not curr_pat.connected:
2339 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2340 return False
2341
2342 enc = 'cp850'
2343 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2344 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2345 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2346
2349
2350
2351
2352
2353
2354
2355
2363
2371
2374
2381
2385
2389
2392
2395
2398
2401
2404
2407
2410
2413
2416
2419
2422
2425
2428
2433
2435 """Cleanup helper.
2436
2437 - should ALWAYS be called when this program is
2438 to be terminated
2439 - ANY code that should be executed before a
2440 regular shutdown should go in here
2441 - framework still functional
2442 """
2443 _log.debug('gmTopLevelFrame._clean_exit() start')
2444
2445
2446 listener = gmBackendListener.gmBackendListener()
2447 try:
2448 listener.shutdown()
2449 except:
2450 _log.exception('cannot stop backend notifications listener thread')
2451
2452
2453 if _scripting_listener is not None:
2454 try:
2455 _scripting_listener.shutdown()
2456 except:
2457 _log.exception('cannot stop scripting listener thread')
2458
2459
2460 self.clock_update_timer.Stop()
2461 gmTimer.shutdown()
2462 gmPhraseWheel.shutdown()
2463
2464
2465 for call_back in self.__pre_exit_callbacks:
2466 try:
2467 call_back()
2468 except:
2469 print "*** pre-exit callback failed ***"
2470 print call_back
2471 _log.exception('callback [%s] failed', call_back)
2472
2473
2474 gmDispatcher.send(u'application_closing')
2475
2476
2477 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2478
2479
2480 curr_width, curr_height = self.GetClientSizeTuple()
2481 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2482 dbcfg = gmCfg.cCfgSQL()
2483 dbcfg.set (
2484 option = 'main.window.width',
2485 value = curr_width,
2486 workplace = gmSurgery.gmCurrentPractice().active_workplace
2487 )
2488 dbcfg.set (
2489 option = 'main.window.height',
2490 value = curr_height,
2491 workplace = gmSurgery.gmCurrentPractice().active_workplace
2492 )
2493
2494 if _cfg.get(option = 'debug'):
2495 print '---=== GNUmed shutdown ===---'
2496 print _('You have to manually close this window to finalize shutting down GNUmed.')
2497 print _('This is so that you can inspect the console output at your leisure.')
2498 print '---=== GNUmed shutdown ===---'
2499
2500
2501 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2502
2503
2504 import threading
2505 _log.debug("%s active threads", threading.activeCount())
2506 for t in threading.enumerate():
2507 _log.debug('thread %s', t)
2508
2509 _log.debug('gmTopLevelFrame._clean_exit() end')
2510
2511
2512
2514
2515 if _cfg.get(option = 'slave'):
2516 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2517 _cfg.get(option = 'slave personality'),
2518 _cfg.get(option = 'xml-rpc port')
2519 )
2520 else:
2521 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2522
2524 """Update title of main window based on template.
2525
2526 This gives nice tooltips on iconified GNUmed instances.
2527
2528 User research indicates that in the title bar people want
2529 the date of birth, not the age, so please stick to this
2530 convention.
2531 """
2532 args = {}
2533
2534 pat = gmPerson.gmCurrentPatient()
2535 if pat.connected:
2536
2537
2538
2539
2540
2541
2542 args['pat'] = u'%s %s %s (%s) #%d' % (
2543 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2544
2545 pat['firstnames'],
2546 pat['lastnames'],
2547 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2548 pat['pk_identity']
2549 )
2550 else:
2551 args['pat'] = _('no patient')
2552
2553 args['prov'] = u'%s%s.%s' % (
2554 gmTools.coalesce(_provider['title'], u'', u'%s '),
2555 _provider['firstnames'][:1],
2556 _provider['lastnames']
2557 )
2558
2559 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2560
2561 self.SetTitle(self.__title_template % args)
2562
2563
2565 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2566 sb.SetStatusWidths([-1, 225])
2567
2568 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2569 self._cb_update_clock()
2570
2571 self.clock_update_timer.Start(milliseconds = 1000)
2572
2574 """Displays date and local time in the second slot of the status bar"""
2575 t = time.localtime(time.time())
2576 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2577 self.SetStatusText(st,1)
2578
2580 """Lock GNUmed client against unauthorized access"""
2581
2582
2583
2584 return
2585
2587 """Unlock the main notebook widgets
2588 As long as we are not logged into the database backend,
2589 all pages but the 'login' page of the main notebook widget
2590 are locked; i.e. not accessible by the user
2591 """
2592
2593
2594
2595
2596
2597 return
2598
2600 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2601
2603
2605
2606 self.__starting_up = True
2607
2608 gmExceptionHandlingWidgets.install_wx_exception_handler()
2609 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2610
2611
2612
2613
2614 self.SetAppName(u'gnumed')
2615 self.SetVendorName(u'The GNUmed Development Community.')
2616 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2617 paths.init_paths(wx = wx, app_name = u'gnumed')
2618
2619 if not self.__setup_prefs_file():
2620 return False
2621
2622 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2623
2624 self.__guibroker = gmGuiBroker.GuiBroker()
2625 self.__setup_platform()
2626
2627 if not self.__establish_backend_connection():
2628 return False
2629
2630 if not _cfg.get(option = 'skip-update-check'):
2631 self.__check_for_updates()
2632
2633 if _cfg.get(option = 'slave'):
2634 if not self.__setup_scripting_listener():
2635 return False
2636
2637
2638 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640,440))
2639 frame.CentreOnScreen(wx.BOTH)
2640 self.SetTopWindow(frame)
2641 frame.Show(True)
2642
2643 if _cfg.get(option = 'debug'):
2644 self.RedirectStdio()
2645 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2646
2647
2648 print '---=== GNUmed startup ===---'
2649 print _('redirecting STDOUT/STDERR to this log window')
2650 print '---=== GNUmed startup ===---'
2651
2652 self.__setup_user_activity_timer()
2653 self.__register_events()
2654
2655 wx.CallAfter(self._do_after_init)
2656
2657 return True
2658
2660 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2661
2662 - after destroying all application windows and controls
2663 - before wx.Windows internal cleanup
2664 """
2665 _log.debug('gmApp.OnExit() start')
2666
2667 self.__shutdown_user_activity_timer()
2668
2669 if _cfg.get(option = 'debug'):
2670 self.RestoreStdio()
2671 sys.stdin = sys.__stdin__
2672 sys.stdout = sys.__stdout__
2673 sys.stderr = sys.__stderr__
2674
2675 _log.debug('gmApp.OnExit() end')
2676
2678 wx.Bell()
2679 wx.Bell()
2680 wx.Bell()
2681 _log.warning('unhandled event detected: QUERY_END_SESSION')
2682 _log.info('we should be saving ourselves from here')
2683 gmLog2.flush()
2684 print "unhandled event detected: QUERY_END_SESSION"
2685
2687 wx.Bell()
2688 wx.Bell()
2689 wx.Bell()
2690 _log.warning('unhandled event detected: END_SESSION')
2691 gmLog2.flush()
2692 print "unhandled event detected: END_SESSION"
2693
2704
2706 self.user_activity_detected = True
2707 evt.Skip()
2708
2710
2711 if self.user_activity_detected:
2712 self.elapsed_inactivity_slices = 0
2713 self.user_activity_detected = False
2714 self.elapsed_inactivity_slices += 1
2715 else:
2716 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2717
2718 pass
2719
2720 self.user_activity_timer.Start(oneShot = True)
2721
2722
2723
2725 try:
2726 kwargs['originated_in_database']
2727 print '==> got notification from database "%s":' % kwargs['signal']
2728 except KeyError:
2729 print '==> received signal from client: "%s"' % kwargs['signal']
2730
2731 del kwargs['signal']
2732 for key in kwargs.keys():
2733 print ' [%s]: %s' % (key, kwargs[key])
2734
2736 print "wx.lib.pubsub message:"
2737 print msg.topic
2738 print msg.data
2739
2745
2747 self.user_activity_detected = True
2748 self.elapsed_inactivity_slices = 0
2749
2750 self.max_user_inactivity_slices = 15
2751 self.user_activity_timer = gmTimer.cTimer (
2752 callback = self._on_user_activity_timer_expired,
2753 delay = 2000
2754 )
2755 self.user_activity_timer.Start(oneShot=True)
2756
2758 try:
2759 self.user_activity_timer.Stop()
2760 del self.user_activity_timer
2761 except:
2762 pass
2763
2765 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2766 wx.EVT_END_SESSION(self, self._on_end_session)
2767
2768
2769
2770
2771
2772 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2773
2774 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2775 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2801
2803 """Handle all the database related tasks necessary for startup."""
2804
2805
2806 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
2807
2808 from Gnumed.wxpython import gmAuthWidgets
2809 connected = gmAuthWidgets.connect_to_database (
2810 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
2811 require_version = not override
2812 )
2813 if not connected:
2814 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
2815 return False
2816
2817
2818 try:
2819 global _provider
2820 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
2821 except ValueError:
2822 account = gmPG2.get_current_user()
2823 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
2824 msg = _(
2825 'The database account [%s] cannot be used as a\n'
2826 'staff member login for GNUmed. There was an\n'
2827 'error retrieving staff details for it.\n\n'
2828 'Please ask your administrator for help.\n'
2829 ) % account
2830 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
2831 return False
2832
2833
2834 tmp = '%s%s %s (%s = %s)' % (
2835 gmTools.coalesce(_provider['title'], ''),
2836 _provider['firstnames'],
2837 _provider['lastnames'],
2838 _provider['short_alias'],
2839 _provider['db_user']
2840 )
2841 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
2842
2843
2844 surgery = gmSurgery.gmCurrentPractice()
2845 msg = surgery.db_logon_banner
2846 if msg.strip() != u'':
2847
2848 login = gmPG2.get_default_login()
2849 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
2850 login.database,
2851 gmTools.coalesce(login.host, u'localhost')
2852 ))
2853 msg = auth + msg + u'\n\n'
2854
2855 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
2856 None,
2857 -1,
2858 caption = _('Verifying database'),
2859 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
2860 button_defs = [
2861 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
2862 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
2863 ]
2864 )
2865 go_on = dlg.ShowModal()
2866 dlg.Destroy()
2867 if go_on != wx.ID_YES:
2868 _log.info('user decided to not connect to this database')
2869 return False
2870
2871
2872 self.__check_db_lang()
2873
2874 return True
2875
2877 """Setup access to a config file for storing preferences."""
2878
2879 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2880
2881 candidates = []
2882 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
2883 if explicit_file is not None:
2884 candidates.append(explicit_file)
2885
2886 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
2887 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
2888 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
2889
2890 prefs_file = None
2891 for candidate in candidates:
2892 try:
2893 open(candidate, 'a+').close()
2894 prefs_file = candidate
2895 break
2896 except IOError:
2897 continue
2898
2899 if prefs_file is None:
2900 msg = _(
2901 'Cannot find configuration file in any of:\n'
2902 '\n'
2903 ' %s\n'
2904 'You may need to use the comand line option\n'
2905 '\n'
2906 ' --conf-file=<FILE>'
2907 ) % '\n '.join(candidates)
2908 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
2909 return False
2910
2911 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
2912 _log.info('user preferences file: %s', prefs_file)
2913
2914 return True
2915
2917
2918 from socket import error as SocketError
2919 from Gnumed.pycommon import gmScriptingListener
2920 from Gnumed.wxpython import gmMacro
2921
2922 slave_personality = gmTools.coalesce (
2923 _cfg.get (
2924 group = u'workplace',
2925 option = u'slave personality',
2926 source_order = [
2927 ('explicit', 'return'),
2928 ('workbase', 'return'),
2929 ('user', 'return'),
2930 ('system', 'return')
2931 ]
2932 ),
2933 u'gnumed-client'
2934 )
2935 _cfg.set_option(option = 'slave personality', value = slave_personality)
2936
2937
2938 port = int (
2939 gmTools.coalesce (
2940 _cfg.get (
2941 group = u'workplace',
2942 option = u'xml-rpc port',
2943 source_order = [
2944 ('explicit', 'return'),
2945 ('workbase', 'return'),
2946 ('user', 'return'),
2947 ('system', 'return')
2948 ]
2949 ),
2950 9999
2951 )
2952 )
2953 _cfg.set_option(option = 'xml-rpc port', value = port)
2954
2955 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
2956 global _scripting_listener
2957 try:
2958 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
2959 except SocketError, e:
2960 _log.exception('cannot start GNUmed XML-RPC server')
2961 gmGuiHelpers.gm_show_error (
2962 aMessage = (
2963 'Cannot start the GNUmed server:\n'
2964 '\n'
2965 ' [%s]'
2966 ) % e,
2967 aTitle = _('GNUmed startup')
2968 )
2969 return False
2970
2971 return True
2972
2992
2994 if gmI18N.system_locale is None or gmI18N.system_locale == '':
2995 _log.warning("system locale is undefined (probably meaning 'C')")
2996 return True
2997
2998
2999 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3000 db_lang = rows[0]['lang']
3001
3002 if db_lang is None:
3003 _log.debug("database locale currently not set")
3004 msg = _(
3005 "There is no language selected in the database for user [%s].\n"
3006 "Your system language is currently set to [%s].\n\n"
3007 "Do you want to set the database language to '%s' ?\n\n"
3008 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3009 checkbox_msg = _('Remember to ignore missing language')
3010 else:
3011 _log.debug("current database locale: [%s]" % db_lang)
3012 msg = _(
3013 "The currently selected database language ('%s') does\n"
3014 "not match the current system language ('%s').\n"
3015 "\n"
3016 "Do you want to set the database language to '%s' ?\n"
3017 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3018 checkbox_msg = _('Remember to ignore language mismatch')
3019
3020
3021 if db_lang == gmI18N.system_locale_level['full']:
3022 _log.debug('Database locale (%s) up to date.' % db_lang)
3023 return True
3024 if db_lang == gmI18N.system_locale_level['country']:
3025 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3026 return True
3027 if db_lang == gmI18N.system_locale_level['language']:
3028 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3029 return True
3030
3031 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3032
3033
3034 ignored_sys_lang = _cfg.get (
3035 group = u'backend',
3036 option = u'ignored mismatching system locale',
3037 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3038 )
3039
3040
3041 if gmI18N.system_locale == ignored_sys_lang:
3042 _log.info('configured to ignore system-to-database locale mismatch')
3043 return True
3044
3045
3046 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3047 None,
3048 -1,
3049 caption = _('Checking database language settings'),
3050 question = msg,
3051 button_defs = [
3052 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3053 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3054 ],
3055 show_checkbox = True,
3056 checkbox_msg = checkbox_msg,
3057 checkbox_tooltip = _(
3058 'Checking this will make GNUmed remember your decision\n'
3059 'until the system language is changed.\n'
3060 '\n'
3061 'You can also reactivate this inquiry by removing the\n'
3062 'corresponding "ignore" option from the configuration file\n'
3063 '\n'
3064 ' [%s]'
3065 ) % _cfg.get(option = 'user_preferences_file')
3066 )
3067 decision = dlg.ShowModal()
3068 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3069 dlg.Destroy()
3070
3071 if decision == wx.ID_NO:
3072 if not remember_ignoring_problem:
3073 return True
3074 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3075 gmCfg2.set_option_in_INI_file (
3076 filename = _cfg.get(option = 'user_preferences_file'),
3077 group = 'backend',
3078 option = 'ignored mismatching system locale',
3079 value = gmI18N.system_locale
3080 )
3081 return True
3082
3083
3084 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3085 if len(lang) > 0:
3086
3087
3088 rows, idx = gmPG2.run_rw_queries (
3089 link_obj = None,
3090 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3091 return_data = True
3092 )
3093 if rows[0][0]:
3094 _log.debug("Successfully set database language to [%s]." % lang)
3095 else:
3096 _log.error('Cannot set database language to [%s].' % lang)
3097 continue
3098 return True
3099
3100
3101 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3102 gmPG2.run_rw_queries(queries = [{
3103 'cmd': u'select i18n.force_curr_lang(%s)',
3104 'args': [gmI18N.system_locale_level['country']]
3105 }])
3106
3107 return True
3108
3110 try:
3111 kwargs['originated_in_database']
3112 print '==> got notification from database "%s":' % kwargs['signal']
3113 except KeyError:
3114 print '==> received signal from client: "%s"' % kwargs['signal']
3115
3116 del kwargs['signal']
3117 for key in kwargs.keys():
3118
3119 try: print ' [%s]: %s' % (key, kwargs[key])
3120 except: print 'cannot print signal information'
3121
3123
3124 try:
3125 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3126 print ' data: %s' % msg.data
3127 print msg
3128 except: print 'problem printing pubsub message information'
3129
3131
3132 if _cfg.get(option = 'debug'):
3133 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3134 _log.debug('gmDispatcher signal monitor activated')
3135 wx.lib.pubsub.Publisher().subscribe (
3136 listener = _signal_debugging_monitor_pubsub,
3137 topic = wx.lib.pubsub.getStrAllTopics()
3138 )
3139 _log.debug('wx.lib.pubsub signal monitor activated')
3140
3141
3142
3143
3144 app = gmApp(redirect = False, clearSigInt = False)
3145 app.MainLoop()
3146
3147
3148
3149 if __name__ == '__main__':
3150
3151 from GNUmed.pycommon import gmI18N
3152 gmI18N.activate_locale()
3153 gmI18N.install_domain()
3154
3155 _log.info('Starting up as main module.')
3156 main()
3157
3158
3159