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
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 self.__gb = gmGuiBroker.GuiBroker()
98 self.__pre_exit_callbacks = []
99 self.bar_width = -1
100 self.menu_id2plugin = {}
101
102 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
103
104 self.__setup_main_menu()
105 self.setup_statusbar()
106 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
107 gmTools.coalesce(_provider['title'], ''),
108 _provider['firstnames'][:1],
109 _provider['lastnames'],
110 _provider['short_alias'],
111 _provider['db_user']
112 ))
113
114 self.__set_window_title_template()
115 self.__update_window_title()
116
117
118
119
120
121 self.SetIcon(gmTools.get_icon(wx = wx))
122
123 self.__register_events()
124
125 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
126 self.vbox = wx.BoxSizer(wx.VERTICAL)
127 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
128
129 self.SetAutoLayout(True)
130 self.SetSizerAndFit(self.vbox)
131
132
133
134
135
136 self.__set_GUI_size()
137
139 """Try to get previous window size from backend."""
140
141 cfg = gmCfg.cCfgSQL()
142
143
144 width = int(cfg.get2 (
145 option = 'main.window.width',
146 workplace = gmSurgery.gmCurrentPractice().active_workplace,
147 bias = 'workplace',
148 default = 800
149 ))
150
151
152 height = int(cfg.get2 (
153 option = 'main.window.height',
154 workplace = gmSurgery.gmCurrentPractice().active_workplace,
155 bias = 'workplace',
156 default = 600
157 ))
158
159 dw = wx.DisplaySize()[0]
160 dh = wx.DisplaySize()[1]
161
162 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
163 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
164 _log.debug('previous GUI size [%s:%s]', width, height)
165
166
167 if width > dw:
168 _log.debug('adjusting GUI width from %s to %s', width, dw)
169 width = dw
170
171 if height > dh:
172 _log.debug('adjusting GUI height from %s to %s', height, dh)
173 height = dh
174
175
176 if width < 100:
177 _log.debug('adjusting GUI width to minimum of 100 pixel')
178 width = 100
179 if height < 100:
180 _log.debug('adjusting GUI height to minimum of 100 pixel')
181 height = 100
182
183 _log.info('setting GUI to size [%s:%s]', width, height)
184
185 self.SetClientSize(wx.Size(width, height))
186
188 """Create the main menu entries.
189
190 Individual entries are farmed out to the modules.
191 """
192 global wx
193 self.mainmenu = wx.MenuBar()
194 self.__gb['main.mainmenu'] = self.mainmenu
195
196
197 menu_gnumed = wx.Menu()
198
199 self.menu_plugins = wx.Menu()
200 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
201
202 ID = wx.NewId()
203 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
204 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
205
206 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
207 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
208
209
210 menu_gnumed.AppendSeparator()
211
212
213 menu_config = wx.Menu()
214 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
215
216 item = menu_config.Append(-1, _('List configuration'), _('List all configuration items stored in the database.'))
217 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
218
219
220 menu_cfg_db = wx.Menu()
221 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
222
223 ID = wx.NewId()
224 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
225 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
226
227 ID = wx.NewId()
228 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
229 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
230
231
232 menu_cfg_client = wx.Menu()
233 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
234
235 ID = wx.NewId()
236 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
237 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
238
239 ID = wx.NewId()
240 menu_cfg_client.Append(ID, _('Temporary directory'), _('Configure the directory to use as scratch space for temporary files.'))
241 wx.EVT_MENU(self, ID, self.__on_configure_temp_dir)
242
243 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
244 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
245
246
247 menu_cfg_ui = wx.Menu()
248 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
249
250
251 menu_cfg_doc = wx.Menu()
252 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
253
254 ID = wx.NewId()
255 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
256 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
257
258 ID = wx.NewId()
259 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
260 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
261
262 ID = wx.NewId()
263 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
264 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
265
266
267 menu_cfg_update = wx.Menu()
268 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
269
270 ID = wx.NewId()
271 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
272 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
273
274 ID = wx.NewId()
275 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
276 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
277
278 ID = wx.NewId()
279 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
280 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
281
282
283 menu_cfg_pat_search = wx.Menu()
284 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
285
286 ID = wx.NewId()
287 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
288 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
289
290 ID = wx.NewId()
291 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
292 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
293
294 ID = wx.NewId()
295 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
296 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
297
298 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
299 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
300
301 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
302 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
303
304
305 menu_cfg_soap_editing = wx.Menu()
306 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
307
308 ID = wx.NewId()
309 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
310 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
311
312
313 menu_cfg_ext_tools = wx.Menu()
314 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
315
316
317
318
319
320 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
321 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
322
323 ID = wx.NewId()
324 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
325 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
326
327 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
328 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
329
330 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
331 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
332
333 item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.'))
334 self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item)
335
336 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
337 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
338
339 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
340 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
341
342 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
343 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
344
345
346 menu_cfg_emr = wx.Menu()
347 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
348
349 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
350 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
351
352
353 menu_cfg_encounter = wx.Menu()
354 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
355
356 ID = wx.NewId()
357 menu_cfg_encounter.Append(ID, _('Edit on patient change'), _('Edit encounter details on changing of patients.'))
358 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
359
360 ID = wx.NewId()
361 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
362 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
363
364 ID = wx.NewId()
365 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
366 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
367
368 ID = wx.NewId()
369 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
370 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
371
372 ID = wx.NewId()
373 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
374 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
375
376
377 menu_cfg_episode = wx.Menu()
378 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
379
380 ID = wx.NewId()
381 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
382 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
383
384
385 menu_master_data = wx.Menu()
386 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
387
388 item = menu_master_data.Append(-1, _('Workplace profiles'), _('Manage the plugins to load per workplace.'))
389 self.Bind(wx.EVT_MENU, self.__on_configure_workplace, item)
390
391 menu_master_data.AppendSeparator()
392
393 item = menu_master_data.Append(-1, _('&Document types'), _('Manage the document types available in the system.'))
394 self.Bind(wx.EVT_MENU, self.__on_edit_doc_types, item)
395
396 item = menu_master_data.Append(-1, _('&Form templates'), _('Manage templates for forms and letters.'))
397 self.Bind(wx.EVT_MENU, self.__on_manage_form_templates, item)
398
399 item = menu_master_data.Append(-1, _('&Text expansions'), _('Manage keyword based text expansion macros.'))
400 self.Bind(wx.EVT_MENU, self.__on_manage_text_expansion, item)
401
402 menu_master_data.AppendSeparator()
403
404 item = menu_master_data.Append(-1, _('&Encounter types'), _('Manage encounter types.'))
405 self.Bind(wx.EVT_MENU, self.__on_manage_encounter_types, item)
406
407 item = menu_master_data.Append(-1, _('&Provinces'), _('Manage provinces (counties, territories, ...).'))
408 self.Bind(wx.EVT_MENU, self.__on_manage_provinces, item)
409
410 menu_master_data.AppendSeparator()
411
412 item = menu_master_data.Append(-1, _('Substances'), _('Manage substances in use.'))
413 self.Bind(wx.EVT_MENU, self.__on_manage_substances, item)
414
415 item = menu_master_data.Append(-1, _('Drugs'), _('Manage branded drugs.'))
416 self.Bind(wx.EVT_MENU, self.__on_manage_branded_drugs, item)
417
418 item = menu_master_data.Append(-1, _('Drug components'), _('Manage components of branded drugs.'))
419 self.Bind(wx.EVT_MENU, self.__on_manage_substances_in_brands, item)
420
421 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
422 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
423
424 menu_master_data.AppendSeparator()
425
426 item = menu_master_data.Append(-1, _('Diagnostic orgs'), _('Manage diagnostic organisations (path labs etc).'))
427 self.Bind(wx.EVT_MENU, self.__on_manage_test_orgs, item)
428
429 item = menu_master_data.Append(-1, _('&Test types'), _('Manage test/measurement types.'))
430 self.Bind(wx.EVT_MENU, self.__on_manage_test_types, item)
431
432 item = menu_master_data.Append(-1, _('&Meta test types'), _('Show meta test/measurement types.'))
433 self.Bind(wx.EVT_MENU, self.__on_manage_meta_test_types, item)
434
435 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
436 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
437
438 menu_master_data.AppendSeparator()
439
440 item = menu_master_data.Append(-1, _('Vaccines'), _('Show known vaccines.'))
441 self.Bind(wx.EVT_MENU, self.__on_manage_vaccines, item)
442
443 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
444 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
445
446 item = menu_master_data.Append(-1, _('Indications'), _('Show known vaccination indications.'))
447 self.Bind(wx.EVT_MENU, self.__on_manage_vaccination_indications, item)
448
449
450 menu_users = wx.Menu()
451 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
452
453 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
454 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
455
456 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
457 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
458
459
460 menu_gnumed.AppendSeparator()
461
462 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
463 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
464
465 self.mainmenu.Append(menu_gnumed, '&GNUmed')
466
467
468 menu_patient = wx.Menu()
469
470 ID_CREATE_PATIENT = wx.NewId()
471 menu_patient.Append(ID_CREATE_PATIENT, _('Register person'), _("Register a new person with GNUmed"))
472 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
473
474
475
476
477 ID_LOAD_EXT_PAT = wx.NewId()
478 menu_patient.Append(ID_LOAD_EXT_PAT, _('Load external'), _('Load and possibly create person from an external source.'))
479 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
480
481 ID_DEL_PAT = wx.NewId()
482 menu_patient.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
483 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
484
485 item = menu_patient.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
486 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
487
488 menu_patient.AppendSeparator()
489
490 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
491 menu_patient.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
492 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
493
494
495 ID = wx.NewId()
496 menu_patient.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
497 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
498
499 menu_patient.AppendSeparator()
500
501 self.mainmenu.Append(menu_patient, '&Person')
502 self.__gb['main.patientmenu'] = menu_patient
503
504
505 menu_emr = wx.Menu()
506 self.mainmenu.Append(menu_emr, _("&EMR"))
507 self.__gb['main.emrmenu'] = menu_emr
508
509
510 menu_emr_show = wx.Menu()
511 menu_emr.AppendMenu(wx.NewId(), _('Show as ...'), menu_emr_show)
512 self.__gb['main.emr_showmenu'] = menu_emr_show
513
514
515 item = menu_emr_show.Append(-1, _('Summary'), _('Show a high-level summary of the EMR.'))
516 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
517
518
519 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
520 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
521
522 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
523 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
524
525
526 menu_emr_edit = wx.Menu()
527 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
528
529 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'))
530 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
531
532 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
533 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
534
535 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
536 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
537
538 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
539 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
540
541 item = menu_emr_edit.Append(-1, _('&Hospital stays'), _('Manage hospital stays.'))
542 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
543
544 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
545 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
546
547 item = menu_emr_edit.Append(-1, _('&Measurement(s)'), _('Add (a) measurement result(s) for the current patient.'))
548 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
549
550 item = menu_emr_edit.Append(-1, _('&Vaccination(s)'), _('Add (a) vaccination(s) for the current patient.'))
551 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
552
553
554
555
556
557
558
559 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
560 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
561
562
563 item = menu_emr.Append(-1, _('&Encounters list'), _('List all encounters including empty ones.'))
564 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
565
566
567 menu_emr.AppendSeparator()
568
569 menu_emr_export = wx.Menu()
570 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
571
572 ID_EXPORT_EMR_ASCII = wx.NewId()
573 menu_emr_export.Append (
574 ID_EXPORT_EMR_ASCII,
575 _('Text document'),
576 _("Export the EMR of the active patient into a text file")
577 )
578 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
579
580 ID_EXPORT_EMR_JOURNAL = wx.NewId()
581 menu_emr_export.Append (
582 ID_EXPORT_EMR_JOURNAL,
583 _('Journal'),
584 _("Export the EMR of the active patient as a chronological journal into a text file")
585 )
586 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
587
588 ID_EXPORT_MEDISTAR = wx.NewId()
589 menu_emr_export.Append (
590 ID_EXPORT_MEDISTAR,
591 _('MEDISTAR import format'),
592 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
593 )
594 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
595
596
597 menu_emr.AppendSeparator()
598
599
600 menu_paperwork = wx.Menu()
601
602 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
603 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
604
605 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
606
607
608 self.menu_tools = wx.Menu()
609 self.__gb['main.toolsmenu'] = self.menu_tools
610 self.mainmenu.Append(self.menu_tools, _("&Tools"))
611
612 ID_DICOM_VIEWER = wx.NewId()
613 viewer = _('no viewer installed')
614 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
615 viewer = u'OsiriX'
616 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
617 viewer = u'Aeskulap'
618 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
619 viewer = u'AMIDE'
620 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
621 viewer = u'(x)medcon'
622 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)
623 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
624 if viewer == _('no viewer installed'):
625 _log.info('neither of OsiriX / Aeskulap / AMIDE / xmedcon found, disabling "DICOM viewer" menu item')
626 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
627
628
629
630
631
632 ID = wx.NewId()
633 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
634 wx.EVT_MENU(self, ID, self.__on_snellen)
635
636 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
637 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
638
639 self.menu_tools.AppendSeparator()
640
641
642 menu_knowledge = wx.Menu()
643 self.__gb['main.knowledgemenu'] = menu_knowledge
644 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
645
646 menu_drug_dbs = wx.Menu()
647 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
648
649 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
650 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
651
652
653
654
655
656
657 menu_id = wx.NewId()
658 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
659 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
660
661
662
663
664 ID_MEDICAL_LINKS = wx.NewId()
665 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
666 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
667
668
669 self.menu_office = wx.Menu()
670
671 self.__gb['main.officemenu'] = self.menu_office
672 self.mainmenu.Append(self.menu_office, _('&Office'))
673
674 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
675 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
676
677 self.menu_office.AppendSeparator()
678
679
680 help_menu = wx.Menu()
681
682 ID = wx.NewId()
683 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
684 wx.EVT_MENU(self, ID, self.__on_display_wiki)
685
686 ID = wx.NewId()
687 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
688 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
689
690 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
691 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
692
693 menu_debugging = wx.Menu()
694 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
695
696 ID_SCREENSHOT = wx.NewId()
697 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
698 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
699
700 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
701 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
702
703 ID = wx.NewId()
704 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
705 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
706
707 ID = wx.NewId()
708 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
709 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
710
711 ID_UNBLOCK = wx.NewId()
712 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
713 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
714
715 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
716 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
717
718
719
720
721 if _cfg.get(option = 'debug'):
722 ID_TOGGLE_PAT_LOCK = wx.NewId()
723 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient'), _('Lock/unlock patient - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
724 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
725
726 ID_TEST_EXCEPTION = wx.NewId()
727 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
728 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
729
730 ID = wx.NewId()
731 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
732 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
733 try:
734 import wx.lib.inspection
735 except ImportError:
736 menu_debugging.Enable(id = ID, enable = False)
737
738 help_menu.AppendSeparator()
739
740 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
741 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
742
743 ID_CONTRIBUTORS = wx.NewId()
744 help_menu.Append(ID_CONTRIBUTORS, _('GNUmed contributors'), _('show GNUmed contributors'))
745 wx.EVT_MENU(self, ID_CONTRIBUTORS, self.__on_show_contributors)
746
747 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
748 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
749
750 help_menu.AppendSeparator()
751
752
753 self.__gb['main.helpmenu'] = help_menu
754 self.mainmenu.Append(help_menu, _("&Help"))
755
756
757
758 self.SetMenuBar(self.mainmenu)
759
762
763
764
766 """register events we want to react to"""
767
768 wx.EVT_CLOSE(self, self.OnClose)
769 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
770 wx.EVT_END_SESSION(self, self._on_end_session)
771
772 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
773 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
774 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
775 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
776 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
777 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
778 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
779 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
780
781 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
782
783 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
784
785 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
786
787 _log.debug('registering plugin with menu system')
788 _log.debug(' generic name: %s', plugin_name)
789 _log.debug(' class name: %s', class_name)
790 _log.debug(' specific menu: %s', menu_name)
791 _log.debug(' menu item: %s', menu_item_name)
792
793
794 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
795 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
796 self.menu_id2plugin[item.Id] = class_name
797
798
799 if menu_name is not None:
800 menu = self.__gb['main.%smenu' % menu_name]
801 item = menu.Append(-1, menu_item_name, menu_help_string)
802 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
803 self.menu_id2plugin[item.Id] = class_name
804
805 return True
806
808 gmDispatcher.send (
809 signal = u'display_widget',
810 name = self.menu_id2plugin[evt.Id]
811 )
812
814 wx.Bell()
815 wx.Bell()
816 wx.Bell()
817 _log.warning('unhandled event detected: QUERY_END_SESSION')
818 _log.info('we should be saving ourselves from here')
819 gmLog2.flush()
820 print "unhandled event detected: QUERY_END_SESSION"
821
823 wx.Bell()
824 wx.Bell()
825 wx.Bell()
826 _log.warning('unhandled event detected: END_SESSION')
827 gmLog2.flush()
828 print "unhandled event detected: END_SESSION"
829
831 if not callable(callback):
832 raise TypeError(u'callback [%s] not callable' % callback)
833
834 self.__pre_exit_callbacks.append(callback)
835
836 - def _on_set_statustext_pubsub(self, context=None):
837 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
838 wx.CallAfter(self.SetStatusText, msg)
839
840 try:
841 if context.data['beep']:
842 wx.Bell()
843 except KeyError:
844 pass
845
846 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
847
848 if msg is None:
849 msg = _('programmer forgot to specify status message')
850
851 if loglevel is not None:
852 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
853
854 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
855 wx.CallAfter(self.SetStatusText, msg)
856
857 if beep:
858 wx.Bell()
859
861 wx.CallAfter(self.__on_db_maintenance_warning)
862
864
865 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
866 wx.Bell()
867 if not wx.GetApp().IsActive():
868 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
869
870 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
871
872 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
873 None,
874 -1,
875 caption = _('Database shutdown warning'),
876 question = _(
877 'The database will be shut down for maintenance\n'
878 'in a few minutes.\n'
879 '\n'
880 'In order to not suffer any loss of data you\n'
881 'will need to save your current work and log\n'
882 'out of this GNUmed client.\n'
883 ),
884 button_defs = [
885 {
886 u'label': _('Close now'),
887 u'tooltip': _('Close this GNUmed client immediately.'),
888 u'default': False
889 },
890 {
891 u'label': _('Finish work'),
892 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
893 u'default': True
894 }
895 ]
896 )
897 decision = dlg.ShowModal()
898 if decision == wx.ID_YES:
899 top_win = wx.GetApp().GetTopWindow()
900 wx.CallAfter(top_win.Close)
901
903 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
904
906
907 if not wx.GetApp().IsActive():
908 if urgent:
909 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
910 else:
911 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
912
913 if msg is not None:
914 self.SetStatusText(msg)
915
916 if urgent:
917 wx.Bell()
918
919 gmHooks.run_hook_script(hook = u'request_user_attention')
920
922 wx.CallAfter(self.__on_pat_name_changed)
923
925 self.__update_window_title()
926
928 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
929
931 self.__update_window_title()
932 try:
933 gmHooks.run_hook_script(hook = u'post_patient_activation')
934 except:
935 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
936 raise
937
939 return self.__sanity_check_encounter()
940
998
999
1000
1003
1011
1014
1015
1016
1031
1054
1056 from Gnumed.wxpython import gmAbout
1057 contribs = gmAbout.cContributorsDlg (
1058 parent = self,
1059 id = -1,
1060 title = _('GNUmed contributors'),
1061 size = wx.Size(400,600),
1062 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1063 )
1064 contribs.ShowModal()
1065 del contribs
1066 del gmAbout
1067
1068
1069
1071 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1072 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1073 self.Close(True)
1074 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1075
1078
1080 send = gmGuiHelpers.gm_show_question (
1081 _('This will send a notification about database downtime\n'
1082 'to all GNUmed clients connected to your database.\n'
1083 '\n'
1084 'Do you want to send the notification ?\n'
1085 ),
1086 _('Announcing database maintenance downtime')
1087 )
1088 if not send:
1089 return
1090 gmPG2.send_maintenance_notification()
1091
1092
1095
1096
1097
1129
1142
1143 gmCfgWidgets.configure_string_option (
1144 message = _(
1145 'Some network installations cannot cope with loading\n'
1146 'documents of arbitrary size in one piece from the\n'
1147 'database (mainly observed on older Windows versions)\n.'
1148 '\n'
1149 'Under such circumstances documents need to be retrieved\n'
1150 'in chunks and reassembled on the client.\n'
1151 '\n'
1152 'Here you can set the size (in Bytes) above which\n'
1153 'GNUmed will retrieve documents in chunks. Setting this\n'
1154 'value to 0 will disable the chunking protocol.'
1155 ),
1156 option = 'horstspace.blob_export_chunk_size',
1157 bias = 'workplace',
1158 default_value = 1024 * 1024,
1159 validator = is_valid
1160 )
1161
1162
1163
1231
1235
1236
1237
1246
1247 gmCfgWidgets.configure_string_option (
1248 message = _(
1249 'When GNUmed cannot find an OpenOffice server it\n'
1250 'will try to start one. OpenOffice, however, needs\n'
1251 'some time to fully start up.\n'
1252 '\n'
1253 'Here you can set the time for GNUmed to wait for OOo.\n'
1254 ),
1255 option = 'external.ooo.startup_settle_time',
1256 bias = 'workplace',
1257 default_value = 2.0,
1258 validator = is_valid
1259 )
1260
1263
1275
1276 gmCfgWidgets.configure_string_option (
1277 message = _(
1278 'GNUmed will use this URL to access a website which lets\n'
1279 'you report an adverse drug reaction (ADR).\n'
1280 '\n'
1281 'You can leave this empty but to set it to a specific\n'
1282 'address the URL must be accessible now.'
1283 ),
1284 option = 'external.urls.report_ADR',
1285 bias = 'user',
1286 default_value = u'https://dcgma.org/uaw/meldung.php',
1287 validator = is_valid
1288 )
1289
1301
1302 gmCfgWidgets.configure_string_option (
1303 message = _(
1304 'GNUmed will use this URL to access a website which lets\n'
1305 'you report an adverse vaccination reaction (vADR).\n'
1306 '\n'
1307 'If you set it to a specific address that URL must be\n'
1308 'accessible now. If you leave it empty it will fall back\n'
1309 'to the URL for reporting other adverse drug reactions.'
1310 ),
1311 option = 'external.urls.report_vaccine_ADR',
1312 bias = 'user',
1313 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',
1314 validator = is_valid
1315 )
1316
1328
1329 gmCfgWidgets.configure_string_option (
1330 message = _(
1331 'GNUmed will use this URL to access an encyclopedia of\n'
1332 'measurement/lab methods from within the measurments grid.\n'
1333 '\n'
1334 'You can leave this empty but to set it to a specific\n'
1335 'address the URL must be accessible now.'
1336 ),
1337 option = 'external.urls.measurements_encyclopedia',
1338 bias = 'user',
1339 default_value = u'http://www.laborlexikon.de',
1340 validator = is_valid
1341 )
1342
1355
1356 gmCfgWidgets.configure_string_option (
1357 message = _(
1358 'Enter the shell command with which to start the\n'
1359 'the ACS risk assessment calculator.\n'
1360 '\n'
1361 'GNUmed will try to verify the path which may,\n'
1362 'however, fail if you are using an emulator such\n'
1363 'as Wine. Nevertheless, starting the calculator\n'
1364 'will work as long as the shell command is correct\n'
1365 'despite the failing test.'
1366 ),
1367 option = 'external.tools.acs_risk_calculator_cmd',
1368 bias = 'user',
1369 validator = is_valid
1370 )
1371
1374
1387
1388 gmCfgWidgets.configure_string_option (
1389 message = _(
1390 'Enter the shell command with which to start\n'
1391 'the FreeDiams drug database frontend.\n'
1392 '\n'
1393 'GNUmed will try to verify that path.'
1394 ),
1395 option = 'external.tools.freediams_cmd',
1396 bias = 'workplace',
1397 default_value = None,
1398 validator = is_valid
1399 )
1400
1413
1414 gmCfgWidgets.configure_string_option (
1415 message = _(
1416 'Enter the shell command with which to start the\n'
1417 'the IFAP drug database.\n'
1418 '\n'
1419 'GNUmed will try to verify the path which may,\n'
1420 'however, fail if you are using an emulator such\n'
1421 'as Wine. Nevertheless, starting IFAP will work\n'
1422 'as long as the shell command is correct despite\n'
1423 'the failing test.'
1424 ),
1425 option = 'external.ifap-win.shell_command',
1426 bias = 'workplace',
1427 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1428 validator = is_valid
1429 )
1430
1431
1432
1481
1482
1483
1500
1503
1506
1511
1512 gmCfgWidgets.configure_string_option (
1513 message = _(
1514 'When a patient is activated GNUmed checks the\n'
1515 "proximity of the patient's birthday.\n"
1516 '\n'
1517 'If the birthday falls within the range of\n'
1518 ' "today %s <the interval you set here>"\n'
1519 'GNUmed will remind you of the recent or\n'
1520 'imminent anniversary.'
1521 ) % u'\u2213',
1522 option = u'patient_search.dob_warn_interval',
1523 bias = 'user',
1524 default_value = '1 week',
1525 validator = is_valid
1526 )
1527
1529
1530 gmCfgWidgets.configure_boolean_option (
1531 parent = self,
1532 question = _(
1533 'When adding progress notes do you want to\n'
1534 'allow opening several unassociated, new\n'
1535 'episodes for a patient at once ?\n'
1536 '\n'
1537 'This can be particularly helpful when entering\n'
1538 'progress notes on entirely new patients presenting\n'
1539 'with a multitude of problems on their first visit.'
1540 ),
1541 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1542 button_tooltips = [
1543 _('Yes, allow for multiple new episodes concurrently.'),
1544 _('No, only allow editing one new episode at a time.')
1545 ]
1546 )
1547
1593
1594
1595
1598
1612
1614 gmCfgWidgets.configure_boolean_option (
1615 parent = self,
1616 question = _(
1617 'Do you want GNUmed to show the encounter\n'
1618 'details editor when changing the active patient ?'
1619 ),
1620 option = 'encounter.show_editor_before_patient_change',
1621 button_tooltips = [
1622 _('Yes, show the encounter editor if it seems appropriate.'),
1623 _('No, never show the encounter editor even if it would seem useful.')
1624 ]
1625 )
1626
1631
1632 gmCfgWidgets.configure_string_option (
1633 message = _(
1634 'When a patient is activated GNUmed checks the\n'
1635 'chart for encounters lacking any entries.\n'
1636 '\n'
1637 'Any such encounters older than what you set\n'
1638 'here will be removed from the medical record.\n'
1639 '\n'
1640 'To effectively disable removal of such encounters\n'
1641 'set this option to an improbable value.\n'
1642 ),
1643 option = 'encounter.ttl_if_empty',
1644 bias = 'user',
1645 default_value = '1 week',
1646 validator = is_valid
1647 )
1648
1653
1654 gmCfgWidgets.configure_string_option (
1655 message = _(
1656 'When a patient is activated GNUmed checks the\n'
1657 'age of the most recent encounter.\n'
1658 '\n'
1659 'If that encounter is younger than this age\n'
1660 'the existing encounter will be continued.\n'
1661 '\n'
1662 '(If it is really old a new encounter is\n'
1663 ' started, or else GNUmed will ask you.)\n'
1664 ),
1665 option = 'encounter.minimum_ttl',
1666 bias = 'user',
1667 default_value = '1 hour 30 minutes',
1668 validator = is_valid
1669 )
1670
1675
1676 gmCfgWidgets.configure_string_option (
1677 message = _(
1678 'When a patient is activated GNUmed checks the\n'
1679 'age of the most recent encounter.\n'
1680 '\n'
1681 'If that encounter is older than this age\n'
1682 'GNUmed will always start a new encounter.\n'
1683 '\n'
1684 '(If it is very recent the existing encounter\n'
1685 ' is continued, or else GNUmed will ask you.)\n'
1686 ),
1687 option = 'encounter.maximum_ttl',
1688 bias = 'user',
1689 default_value = '6 hours',
1690 validator = is_valid
1691 )
1692
1701
1702 gmCfgWidgets.configure_string_option (
1703 message = _(
1704 'At any time there can only be one open (ongoing)\n'
1705 'episode for each health issue.\n'
1706 '\n'
1707 'When you try to open (add data to) an episode on a health\n'
1708 'issue GNUmed will check for an existing open episode on\n'
1709 'that issue. If there is any it will check the age of that\n'
1710 'episode. The episode is closed if it has been dormant (no\n'
1711 'data added, that is) for the period of time (in days) you\n'
1712 'set here.\n'
1713 '\n'
1714 "If the existing episode hasn't been dormant long enough\n"
1715 'GNUmed will consult you what to do.\n'
1716 '\n'
1717 'Enter maximum episode dormancy in DAYS:'
1718 ),
1719 option = 'episode.ttl',
1720 bias = 'user',
1721 default_value = 60,
1722 validator = is_valid
1723 )
1724
1755
1758
1773
1798
1810
1811 gmCfgWidgets.configure_string_option (
1812 message = _(
1813 'GNUmed can check for new releases being available. To do\n'
1814 'so it needs to load version information from an URL.\n'
1815 '\n'
1816 'The default URL is:\n'
1817 '\n'
1818 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1819 '\n'
1820 'but you can configure any other URL locally. Note\n'
1821 'that you must enter the location as a valid URL.\n'
1822 'Depending on the URL the client will need online\n'
1823 'access when checking for updates.'
1824 ),
1825 option = u'horstspace.update.url',
1826 bias = u'workplace',
1827 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1828 validator = is_valid
1829 )
1830
1848
1865
1876
1877 gmCfgWidgets.configure_string_option (
1878 message = _(
1879 'GNUmed can show the document review dialog after\n'
1880 'calling the appropriate viewer for that document.\n'
1881 '\n'
1882 'Select the conditions under which you want\n'
1883 'GNUmed to do so:\n'
1884 '\n'
1885 ' 0: never display the review dialog\n'
1886 ' 1: always display the dialog\n'
1887 ' 2: only if there is no previous review by me\n'
1888 '\n'
1889 'Note that if a viewer is configured to not block\n'
1890 'GNUmed during document display the review dialog\n'
1891 'will actually appear in parallel to the viewer.'
1892 ),
1893 option = u'horstspace.document_viewer.review_after_display',
1894 bias = u'user',
1895 default_value = 2,
1896 validator = is_valid
1897 )
1898
1912
1914
1915 dbcfg = gmCfg.cCfgSQL()
1916 cmd = dbcfg.get2 (
1917 option = u'external.tools.acs_risk_calculator_cmd',
1918 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1919 bias = 'user'
1920 )
1921
1922 if cmd is None:
1923 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
1924 return
1925
1926 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1927 try:
1928 subprocess.check_call (
1929 args = (cmd,),
1930 close_fds = True,
1931 cwd = cwd
1932 )
1933 except (OSError, ValueError, subprocess.CalledProcessError):
1934 _log.exception('there was a problem executing [%s]', cmd)
1935 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
1936 return
1937
1938 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
1939 for pdf in pdfs:
1940 try:
1941 open(pdf).close()
1942 except:
1943 _log.exception('error accessing [%s]', pdf)
1944 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the ARRIBA result in [%s] !') % pdf, beep = True)
1945 continue
1946
1947 doc = gmDocumentWidgets.save_file_as_new_document (
1948 parent = self,
1949 filename = pdf,
1950 document_type = u'risk assessment'
1951 )
1952
1953 try:
1954 os.remove(pdf)
1955 except StandardError:
1956 _log.exception('cannot remove [%s]', pdf)
1957
1958 if doc is None:
1959 continue
1960 doc['comment'] = u'ARRIBA: %s' % _('cardiovascular risk assessment')
1961 doc.save()
1962
1963 return
1964
1966 dlg = gmSnellen.cSnellenCfgDlg()
1967 if dlg.ShowModal() != wx.ID_OK:
1968 return
1969
1970 frame = gmSnellen.cSnellenChart (
1971 width = dlg.vals[0],
1972 height = dlg.vals[1],
1973 alpha = dlg.vals[2],
1974 mirr = dlg.vals[3],
1975 parent = None
1976 )
1977 frame.CentreOnScreen(wx.BOTH)
1978
1979
1980 frame.Show(True)
1981
1982
1984 webbrowser.open (
1985 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
1986 new = False,
1987 autoraise = True
1988 )
1989
1992
1994 webbrowser.open (
1995 url = 'http://www.kompendium.ch',
1996 new = False,
1997 autoraise = True
1998 )
1999
2000
2001
2005
2006
2007
2009 wx.CallAfter(self.__save_screenshot)
2010 evt.Skip()
2011
2013
2014 time.sleep(0.5)
2015
2016 rect = self.GetRect()
2017
2018
2019 if sys.platform == 'linux2':
2020 client_x, client_y = self.ClientToScreen((0, 0))
2021 border_width = client_x - rect.x
2022 title_bar_height = client_y - rect.y
2023
2024 if self.GetMenuBar():
2025 title_bar_height /= 2
2026 rect.width += (border_width * 2)
2027 rect.height += title_bar_height + border_width
2028
2029 wdc = wx.ScreenDC()
2030 mdc = wx.MemoryDC()
2031 img = wx.EmptyBitmap(rect.width, rect.height)
2032 mdc.SelectObject(img)
2033 mdc.Blit (
2034 0, 0,
2035 rect.width, rect.height,
2036 wdc,
2037 rect.x, rect.y
2038 )
2039
2040
2041 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2042 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2043 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2044
2046
2047 raise ValueError('raised ValueError to test exception handling')
2048
2050 import wx.lib.inspection
2051 wx.lib.inspection.InspectionTool().Show()
2052
2054 webbrowser.open (
2055 url = 'https://bugs.launchpad.net/gnumed/',
2056 new = False,
2057 autoraise = True
2058 )
2059
2061 webbrowser.open (
2062 url = 'http://wiki.gnumed.de',
2063 new = False,
2064 autoraise = True
2065 )
2066
2068 webbrowser.open (
2069 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
2070 new = False,
2071 autoraise = True
2072 )
2073
2075 webbrowser.open (
2076 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
2077 new = False,
2078 autoraise = True
2079 )
2080
2087
2091
2094
2101
2106
2108 name = os.path.basename(gmLog2._logfile_name)
2109 name, ext = os.path.splitext(name)
2110 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2111 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2112
2113 dlg = wx.FileDialog (
2114 parent = self,
2115 message = _("Save current log as..."),
2116 defaultDir = new_path,
2117 defaultFile = new_name,
2118 wildcard = "%s (*.log)|*.log" % _("log files"),
2119 style = wx.SAVE
2120 )
2121 choice = dlg.ShowModal()
2122 new_name = dlg.GetPath()
2123 dlg.Destroy()
2124 if choice != wx.ID_OK:
2125 return True
2126
2127 _log.warning('syncing log file for backup to [%s]', new_name)
2128 gmLog2.flush()
2129 shutil.copy2(gmLog2._logfile_name, new_name)
2130 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2131
2132
2133
2135 """This is the wx.EVT_CLOSE handler.
2136
2137 - framework still functional
2138 """
2139 _log.debug('gmTopLevelFrame.OnClose() start')
2140 self._clean_exit()
2141 self.Destroy()
2142 _log.debug('gmTopLevelFrame.OnClose() end')
2143 return True
2144
2150
2155
2163
2170
2177
2187
2195
2203
2211
2219
2228
2236
2238 pat = gmPerson.gmCurrentPatient()
2239 if not pat.connected:
2240 gmDispatcher.send(signal = 'statustext', msg = _('Cannot show EMR summary. No active patient.'))
2241 return False
2242
2243 emr = pat.get_emr()
2244 dlg = wx.MessageDialog (
2245 parent = self,
2246 message = emr.format_statistics(),
2247 caption = _('EMR Summary'),
2248 style = wx.OK | wx.STAY_ON_TOP
2249 )
2250 dlg.ShowModal()
2251 dlg.Destroy()
2252 return True
2253
2256
2259
2261
2262 pat = gmPerson.gmCurrentPatient()
2263 if not pat.connected:
2264 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2265 return False
2266
2267 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2268
2269 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2270 gmTools.mkdir(aDefDir)
2271
2272 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2273 dlg = wx.FileDialog (
2274 parent = self,
2275 message = _("Save patient's EMR journal as..."),
2276 defaultDir = aDefDir,
2277 defaultFile = fname,
2278 wildcard = aWildcard,
2279 style = wx.SAVE
2280 )
2281 choice = dlg.ShowModal()
2282 fname = dlg.GetPath()
2283 dlg.Destroy()
2284 if choice != wx.ID_OK:
2285 return True
2286
2287 _log.debug('exporting EMR journal to [%s]' % fname)
2288
2289 exporter = gmPatientExporter.cEMRJournalExporter()
2290
2291 wx.BeginBusyCursor()
2292 try:
2293 fname = exporter.export_to_file(filename = fname)
2294 except:
2295 wx.EndBusyCursor()
2296 gmGuiHelpers.gm_show_error (
2297 _('Error exporting patient EMR as chronological journal.'),
2298 _('EMR journal export')
2299 )
2300 raise
2301 wx.EndBusyCursor()
2302
2303 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2304
2305 return True
2306
2313
2323
2325 curr_pat = gmPerson.gmCurrentPatient()
2326 if not curr_pat.connected:
2327 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2328 return False
2329
2330 enc = 'cp850'
2331 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2332 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2333 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2334
2337
2338
2339
2340
2341
2342
2343
2351
2359
2362
2371
2375
2379
2382
2385
2388
2391
2394
2397
2400
2403
2406
2409
2412
2415
2418
2423
2425 """Cleanup helper.
2426
2427 - should ALWAYS be called when this program is
2428 to be terminated
2429 - ANY code that should be executed before a
2430 regular shutdown should go in here
2431 - framework still functional
2432 """
2433 _log.debug('gmTopLevelFrame._clean_exit() start')
2434
2435
2436 listener = gmBackendListener.gmBackendListener()
2437 try:
2438 listener.shutdown()
2439 except:
2440 _log.exception('cannot stop backend notifications listener thread')
2441
2442
2443 if _scripting_listener is not None:
2444 try:
2445 _scripting_listener.shutdown()
2446 except:
2447 _log.exception('cannot stop scripting listener thread')
2448
2449
2450 self.clock_update_timer.Stop()
2451 gmTimer.shutdown()
2452 gmPhraseWheel.shutdown()
2453
2454
2455 for call_back in self.__pre_exit_callbacks:
2456 try:
2457 call_back()
2458 except:
2459 print "*** pre-exit callback failed ***"
2460 print call_back
2461 _log.exception('callback [%s] failed', call_back)
2462
2463
2464 gmDispatcher.send(u'application_closing')
2465
2466
2467 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2468
2469
2470 curr_width, curr_height = self.GetClientSizeTuple()
2471 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2472 dbcfg = gmCfg.cCfgSQL()
2473 dbcfg.set (
2474 option = 'main.window.width',
2475 value = curr_width,
2476 workplace = gmSurgery.gmCurrentPractice().active_workplace
2477 )
2478 dbcfg.set (
2479 option = 'main.window.height',
2480 value = curr_height,
2481 workplace = gmSurgery.gmCurrentPractice().active_workplace
2482 )
2483
2484 if _cfg.get(option = 'debug'):
2485 print '---=== GNUmed shutdown ===---'
2486 print _('You have to manually close this window to finalize shutting down GNUmed.')
2487 print _('This is so that you can inspect the console output at your leisure.')
2488 print '---=== GNUmed shutdown ===---'
2489
2490
2491 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2492
2493
2494 import threading
2495 _log.debug("%s active threads", threading.activeCount())
2496 for t in threading.enumerate():
2497 _log.debug('thread %s', t)
2498
2499 _log.debug('gmTopLevelFrame._clean_exit() end')
2500
2501
2502
2504
2505 if _cfg.get(option = 'slave'):
2506 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2507 _cfg.get(option = 'slave personality'),
2508 _cfg.get(option = 'xml-rpc port')
2509 )
2510 else:
2511 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2512
2514 """Update title of main window based on template.
2515
2516 This gives nice tooltips on iconified GNUmed instances.
2517
2518 User research indicates that in the title bar people want
2519 the date of birth, not the age, so please stick to this
2520 convention.
2521 """
2522 args = {}
2523
2524 pat = gmPerson.gmCurrentPatient()
2525 if pat.connected:
2526
2527
2528
2529
2530
2531
2532 args['pat'] = u'%s %s %s (%s) #%d' % (
2533 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2534
2535 pat['firstnames'],
2536 pat['lastnames'],
2537 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2538 pat['pk_identity']
2539 )
2540 else:
2541 args['pat'] = _('no patient')
2542
2543 args['prov'] = u'%s%s.%s' % (
2544 gmTools.coalesce(_provider['title'], u'', u'%s '),
2545 _provider['firstnames'][:1],
2546 _provider['lastnames']
2547 )
2548
2549 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2550
2551 self.SetTitle(self.__title_template % args)
2552
2553
2555 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2556 sb.SetStatusWidths([-1, 225])
2557
2558 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2559 self._cb_update_clock()
2560
2561 self.clock_update_timer.Start(milliseconds = 1000)
2562
2564 """Displays date and local time in the second slot of the status bar"""
2565 t = time.localtime(time.time())
2566 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2567 self.SetStatusText(st,1)
2568
2570 """Lock GNUmed client against unauthorized access"""
2571
2572
2573
2574 return
2575
2577 """Unlock the main notebook widgets
2578 As long as we are not logged into the database backend,
2579 all pages but the 'login' page of the main notebook widget
2580 are locked; i.e. not accessible by the user
2581 """
2582
2583
2584
2585
2586
2587 return
2588
2590 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2591
2593
2595
2596 self.__starting_up = True
2597
2598 gmExceptionHandlingWidgets.install_wx_exception_handler()
2599 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2600
2601
2602
2603
2604 self.SetAppName(u'gnumed')
2605 self.SetVendorName(u'The GNUmed Development Community.')
2606 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2607 paths.init_paths(wx = wx, app_name = u'gnumed')
2608
2609 if not self.__setup_prefs_file():
2610 return False
2611
2612 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2613
2614 self.__guibroker = gmGuiBroker.GuiBroker()
2615 self.__setup_platform()
2616
2617 if not self.__establish_backend_connection():
2618 return False
2619
2620 if not _cfg.get(option = 'skip-update-check'):
2621 self.__check_for_updates()
2622
2623 if _cfg.get(option = 'slave'):
2624 if not self.__setup_scripting_listener():
2625 return False
2626
2627
2628 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640,440))
2629 frame.CentreOnScreen(wx.BOTH)
2630 self.SetTopWindow(frame)
2631 frame.Show(True)
2632
2633 if _cfg.get(option = 'debug'):
2634 self.RedirectStdio()
2635 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2636
2637
2638 print '---=== GNUmed startup ===---'
2639 print _('redirecting STDOUT/STDERR to this log window')
2640 print '---=== GNUmed startup ===---'
2641
2642 self.__setup_user_activity_timer()
2643 self.__register_events()
2644
2645 wx.CallAfter(self._do_after_init)
2646
2647 return True
2648
2650 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2651
2652 - after destroying all application windows and controls
2653 - before wx.Windows internal cleanup
2654 """
2655 _log.debug('gmApp.OnExit() start')
2656
2657 self.__shutdown_user_activity_timer()
2658
2659 if _cfg.get(option = 'debug'):
2660 self.RestoreStdio()
2661 sys.stdin = sys.__stdin__
2662 sys.stdout = sys.__stdout__
2663 sys.stderr = sys.__stderr__
2664
2665 _log.debug('gmApp.OnExit() end')
2666
2668 wx.Bell()
2669 wx.Bell()
2670 wx.Bell()
2671 _log.warning('unhandled event detected: QUERY_END_SESSION')
2672 _log.info('we should be saving ourselves from here')
2673 gmLog2.flush()
2674 print "unhandled event detected: QUERY_END_SESSION"
2675
2677 wx.Bell()
2678 wx.Bell()
2679 wx.Bell()
2680 _log.warning('unhandled event detected: END_SESSION')
2681 gmLog2.flush()
2682 print "unhandled event detected: END_SESSION"
2683
2694
2696 self.user_activity_detected = True
2697 evt.Skip()
2698
2700
2701 if self.user_activity_detected:
2702 self.elapsed_inactivity_slices = 0
2703 self.user_activity_detected = False
2704 self.elapsed_inactivity_slices += 1
2705 else:
2706 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2707
2708 pass
2709
2710 self.user_activity_timer.Start(oneShot = True)
2711
2712
2713
2715 try:
2716 kwargs['originated_in_database']
2717 print '==> got notification from database "%s":' % kwargs['signal']
2718 except KeyError:
2719 print '==> received signal from client: "%s"' % kwargs['signal']
2720
2721 del kwargs['signal']
2722 for key in kwargs.keys():
2723 print ' [%s]: %s' % (key, kwargs[key])
2724
2726 print "wx.lib.pubsub message:"
2727 print msg.topic
2728 print msg.data
2729
2735
2737 self.user_activity_detected = True
2738 self.elapsed_inactivity_slices = 0
2739
2740 self.max_user_inactivity_slices = 15
2741 self.user_activity_timer = gmTimer.cTimer (
2742 callback = self._on_user_activity_timer_expired,
2743 delay = 2000
2744 )
2745 self.user_activity_timer.Start(oneShot=True)
2746
2748 try:
2749 self.user_activity_timer.Stop()
2750 del self.user_activity_timer
2751 except:
2752 pass
2753
2755 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2756 wx.EVT_END_SESSION(self, self._on_end_session)
2757
2758
2759
2760
2761
2762 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2763
2764 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2765 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2791
2793 """Handle all the database related tasks necessary for startup."""
2794
2795
2796 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
2797
2798 from Gnumed.wxpython import gmAuthWidgets
2799 connected = gmAuthWidgets.connect_to_database (
2800 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
2801 require_version = not override
2802 )
2803 if not connected:
2804 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
2805 return False
2806
2807
2808 try:
2809 global _provider
2810 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
2811 except ValueError:
2812 account = gmPG2.get_current_user()
2813 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
2814 msg = _(
2815 'The database account [%s] cannot be used as a\n'
2816 'staff member login for GNUmed. There was an\n'
2817 'error retrieving staff details for it.\n\n'
2818 'Please ask your administrator for help.\n'
2819 ) % account
2820 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
2821 return False
2822
2823
2824 tmp = '%s%s %s (%s = %s)' % (
2825 gmTools.coalesce(_provider['title'], ''),
2826 _provider['firstnames'],
2827 _provider['lastnames'],
2828 _provider['short_alias'],
2829 _provider['db_user']
2830 )
2831 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
2832
2833
2834 surgery = gmSurgery.gmCurrentPractice()
2835 msg = surgery.db_logon_banner
2836 if msg.strip() != u'':
2837
2838 login = gmPG2.get_default_login()
2839 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
2840 login.database,
2841 gmTools.coalesce(login.host, u'localhost')
2842 ))
2843 msg = auth + msg + u'\n\n'
2844
2845 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
2846 None,
2847 -1,
2848 caption = _('Verifying database'),
2849 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
2850 button_defs = [
2851 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
2852 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
2853 ]
2854 )
2855 go_on = dlg.ShowModal()
2856 dlg.Destroy()
2857 if go_on != wx.ID_YES:
2858 _log.info('user decided to not connect to this database')
2859 return False
2860
2861
2862 self.__check_db_lang()
2863
2864 return True
2865
2867 """Setup access to a config file for storing preferences."""
2868
2869 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2870
2871 candidates = []
2872 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
2873 if explicit_file is not None:
2874 candidates.append(explicit_file)
2875
2876 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
2877 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
2878 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
2879
2880 prefs_file = None
2881 for candidate in candidates:
2882 try:
2883 open(candidate, 'a+').close()
2884 prefs_file = candidate
2885 break
2886 except IOError:
2887 continue
2888
2889 if prefs_file is None:
2890 msg = _(
2891 'Cannot find configuration file in any of:\n'
2892 '\n'
2893 ' %s\n'
2894 'You may need to use the comand line option\n'
2895 '\n'
2896 ' --conf-file=<FILE>'
2897 ) % '\n '.join(candidates)
2898 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
2899 return False
2900
2901 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
2902 _log.info('user preferences file: %s', prefs_file)
2903
2904 return True
2905
2907
2908 from socket import error as SocketError
2909 from Gnumed.pycommon import gmScriptingListener
2910 from Gnumed.wxpython import gmMacro
2911
2912 slave_personality = gmTools.coalesce (
2913 _cfg.get (
2914 group = u'workplace',
2915 option = u'slave personality',
2916 source_order = [
2917 ('explicit', 'return'),
2918 ('workbase', 'return'),
2919 ('user', 'return'),
2920 ('system', 'return')
2921 ]
2922 ),
2923 u'gnumed-client'
2924 )
2925 _cfg.set_option(option = 'slave personality', value = slave_personality)
2926
2927
2928 port = int (
2929 gmTools.coalesce (
2930 _cfg.get (
2931 group = u'workplace',
2932 option = u'xml-rpc port',
2933 source_order = [
2934 ('explicit', 'return'),
2935 ('workbase', 'return'),
2936 ('user', 'return'),
2937 ('system', 'return')
2938 ]
2939 ),
2940 9999
2941 )
2942 )
2943 _cfg.set_option(option = 'xml-rpc port', value = port)
2944
2945 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
2946 global _scripting_listener
2947 try:
2948 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
2949 except SocketError, e:
2950 _log.exception('cannot start GNUmed XML-RPC server')
2951 gmGuiHelpers.gm_show_error (
2952 aMessage = (
2953 'Cannot start the GNUmed server:\n'
2954 '\n'
2955 ' [%s]'
2956 ) % e,
2957 aTitle = _('GNUmed startup')
2958 )
2959 return False
2960
2961 return True
2962
2982
2984 if gmI18N.system_locale is None or gmI18N.system_locale == '':
2985 _log.warning("system locale is undefined (probably meaning 'C')")
2986 return True
2987
2988
2989 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
2990 db_lang = rows[0]['lang']
2991
2992 if db_lang is None:
2993 _log.debug("database locale currently not set")
2994 msg = _(
2995 "There is no language selected in the database for user [%s].\n"
2996 "Your system language is currently set to [%s].\n\n"
2997 "Do you want to set the database language to '%s' ?\n\n"
2998 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
2999 checkbox_msg = _('Remember to ignore missing language')
3000 else:
3001 _log.debug("current database locale: [%s]" % db_lang)
3002 msg = _(
3003 "The currently selected database language ('%s') does\n"
3004 "not match the current system language ('%s').\n"
3005 "\n"
3006 "Do you want to set the database language to '%s' ?\n"
3007 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3008 checkbox_msg = _('Remember to ignore language mismatch')
3009
3010
3011 if db_lang == gmI18N.system_locale_level['full']:
3012 _log.debug('Database locale (%s) up to date.' % db_lang)
3013 return True
3014 if db_lang == gmI18N.system_locale_level['country']:
3015 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3016 return True
3017 if db_lang == gmI18N.system_locale_level['language']:
3018 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3019 return True
3020
3021 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3022
3023
3024 ignored_sys_lang = _cfg.get (
3025 group = u'backend',
3026 option = u'ignored mismatching system locale',
3027 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3028 )
3029
3030
3031 if gmI18N.system_locale == ignored_sys_lang:
3032 _log.info('configured to ignore system-to-database locale mismatch')
3033 return True
3034
3035
3036 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3037 None,
3038 -1,
3039 caption = _('Checking database language settings'),
3040 question = msg,
3041 button_defs = [
3042 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3043 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3044 ],
3045 show_checkbox = True,
3046 checkbox_msg = checkbox_msg,
3047 checkbox_tooltip = _(
3048 'Checking this will make GNUmed remember your decision\n'
3049 'until the system language is changed.\n'
3050 '\n'
3051 'You can also reactivate this inquiry by removing the\n'
3052 'corresponding "ignore" option from the configuration file\n'
3053 '\n'
3054 ' [%s]'
3055 ) % _cfg.get(option = 'user_preferences_file')
3056 )
3057 decision = dlg.ShowModal()
3058 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3059 dlg.Destroy()
3060
3061 if decision == wx.ID_NO:
3062 if not remember_ignoring_problem:
3063 return True
3064 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3065 gmCfg2.set_option_in_INI_file (
3066 filename = _cfg.get(option = 'user_preferences_file'),
3067 group = 'backend',
3068 option = 'ignored mismatching system locale',
3069 value = gmI18N.system_locale
3070 )
3071 return True
3072
3073
3074 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3075 if len(lang) > 0:
3076
3077
3078 rows, idx = gmPG2.run_rw_queries (
3079 link_obj = None,
3080 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3081 return_data = True
3082 )
3083 if rows[0][0]:
3084 _log.debug("Successfully set database language to [%s]." % lang)
3085 else:
3086 _log.error('Cannot set database language to [%s].' % lang)
3087 continue
3088 return True
3089
3090
3091 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3092 gmPG2.run_rw_queries(queries = [{
3093 'cmd': u'select i18n.force_curr_lang(%s)',
3094 'args': [gmI18N.system_locale_level['country']]
3095 }])
3096
3097 return True
3098
3100 try:
3101 kwargs['originated_in_database']
3102 print '==> got notification from database "%s":' % kwargs['signal']
3103 except KeyError:
3104 print '==> received signal from client: "%s"' % kwargs['signal']
3105
3106 del kwargs['signal']
3107 for key in kwargs.keys():
3108
3109 try: print ' [%s]: %s' % (key, kwargs[key])
3110 except: print 'cannot print signal information'
3111
3113
3114 try:
3115 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3116 print ' data: %s' % msg.data
3117 print msg
3118 except: print 'problem printing pubsub message information'
3119
3121
3122 if _cfg.get(option = 'debug'):
3123 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3124 _log.debug('gmDispatcher signal monitor activated')
3125 wx.lib.pubsub.Publisher().subscribe (
3126 listener = _signal_debugging_monitor_pubsub,
3127 topic = wx.lib.pubsub.getStrAllTopics()
3128 )
3129 _log.debug('wx.lib.pubsub signal monitor activated')
3130
3131
3132
3133
3134 app = gmApp(redirect = False, clearSigInt = False)
3135 app.MainLoop()
3136
3137
3138
3139 if __name__ == '__main__':
3140
3141 from GNUmed.pycommon import gmI18N
3142 gmI18N.activate_locale()
3143 gmI18N.install_domain()
3144
3145 _log.info('Starting up as main module.')
3146 main()
3147
3148
3149