1
2 """GNUmed GUI client.
3
4 This contains the GUI application framework and main window
5 of the all signing all dancing GNUmed Python Reference
6 client. It relies on the <gnumed.py> launcher having set up
7 the non-GUI-related runtime environment.
8
9 This source code is protected by the GPL licensing scheme.
10 Details regarding the GPL are available at http://www.gnu.org
11 You may use and share it as long as you don't deny this right
12 to anybody else.
13
14 copyright: authors
15 """
16
17 __version__ = "$Revision: 1.491 $"
18 __author__ = "H. Herb <hherb@gnumed.net>,\
19 K. Hilbert <Karsten.Hilbert@gmx.net>,\
20 I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
21 __license__ = 'GPL (details at http://www.gnu.org)'
22
23
24 import sys, time, os, locale, os.path, datetime as pyDT
25 import webbrowser, shutil, logging, urllib2, subprocess, glob
26
27
28
29
30 if not hasattr(sys, 'frozen'):
31 import wxversion
32 wxversion.ensureMinimal('2.8-unicode', optionsRequired=True)
33
34 try:
35 import wx
36 import wx.lib.pubsub
37 except ImportError:
38 print "GNUmed startup: Cannot import wxPython library."
39 print "GNUmed startup: Make sure wxPython is installed."
40 print 'CRITICAL ERROR: Error importing wxPython. Halted.'
41 raise
42
43
44
45 version = int(u'%s%s' % (wx.MAJOR_VERSION, wx.MINOR_VERSION))
46 if (version < 28) or ('unicode' not in wx.PlatformInfo):
47 print "GNUmed startup: Unsupported wxPython version (%s: %s)." % (wx.VERSION_STRING, wx.PlatformInfo)
48 print "GNUmed startup: wxPython 2.8+ with unicode support is required."
49 print 'CRITICAL ERROR: Proper wxPython version not found. Halted.'
50 raise ValueError('wxPython 2.8+ with unicode support not found')
51
52
53
54 from Gnumed.pycommon import gmCfg, gmPG2, gmDispatcher, gmGuiBroker, gmI18N
55 from Gnumed.pycommon import gmExceptions, gmShellAPI, gmTools, gmDateTime
56 from Gnumed.pycommon import gmHooks, gmBackendListener, gmCfg2, gmLog2
57
58 from Gnumed.business import gmPerson, gmClinicalRecord, gmSurgery, gmEMRStructItems
59 from Gnumed.business import gmVaccination
60
61 from Gnumed.exporters import gmPatientExporter
62
63 from Gnumed.wxpython import gmGuiHelpers, gmHorstSpace, gmEMRBrowser
64 from Gnumed.wxpython import gmDemographicsWidgets, gmEMRStructWidgets
65 from Gnumed.wxpython import gmPatSearchWidgets, gmAllergyWidgets, gmListWidgets
66 from Gnumed.wxpython import gmProviderInboxWidgets, gmCfgWidgets, gmExceptionHandlingWidgets
67 from Gnumed.wxpython import gmNarrativeWidgets, gmPhraseWheel, gmMedicationWidgets
68 from Gnumed.wxpython import gmStaffWidgets, gmDocumentWidgets, gmTimer, gmMeasurementWidgets
69 from Gnumed.wxpython import gmFormWidgets, gmSnellen, gmVaccWidgets, gmPersonContactWidgets
70 from Gnumed.wxpython import gmI18nWidgets, gmCodingWidgets
71 from Gnumed.wxpython import gmOrganizationWidgets
72
73
74 try:
75 _('dummy-no-need-to-translate-but-make-epydoc-happy')
76 except NameError:
77 _ = lambda x:x
78
79 _cfg = gmCfg2.gmCfgData()
80 _provider = None
81 _scripting_listener = None
82
83 _log = logging.getLogger('gm.main')
84 _log.info(__version__)
85 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
86
87
89 """GNUmed client's main windows frame.
90
91 This is where it all happens. Avoid popping up any other windows.
92 Most user interaction should happen to and from widgets within this frame
93 """
94
95 - def __init__(self, parent, id, title, size=wx.DefaultSize):
96 """You'll have to browse the source to understand what the constructor does
97 """
98 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
99
100 if wx.Platform == '__WXMSW__':
101 font = self.GetFont()
102 _log.debug('default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
103 desired_font_face = u'DejaVu Sans'
104 success = font.SetFaceName(desired_font_face)
105 if success:
106 self.SetFont(font)
107 _log.debug('setting font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
108 else:
109 font = self.GetFont()
110 _log.error('cannot set font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), desired_font_face)
111
112 self.__gb = gmGuiBroker.GuiBroker()
113 self.__pre_exit_callbacks = []
114 self.bar_width = -1
115 self.menu_id2plugin = {}
116
117 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
118
119 self.__setup_main_menu()
120 self.setup_statusbar()
121 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
122 gmTools.coalesce(_provider['title'], ''),
123 _provider['firstnames'][:1],
124 _provider['lastnames'],
125 _provider['short_alias'],
126 _provider['db_user']
127 ))
128
129 self.__set_window_title_template()
130 self.__update_window_title()
131
132
133
134
135
136 self.SetIcon(gmTools.get_icon(wx = wx))
137
138 self.__register_events()
139
140 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
141 self.vbox = wx.BoxSizer(wx.VERTICAL)
142 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
143
144 self.SetAutoLayout(True)
145 self.SetSizerAndFit(self.vbox)
146
147
148
149
150
151 self.__set_GUI_size()
152
154 """Try to get previous window size from backend."""
155
156 cfg = gmCfg.cCfgSQL()
157
158
159 width = int(cfg.get2 (
160 option = 'main.window.width',
161 workplace = gmSurgery.gmCurrentPractice().active_workplace,
162 bias = 'workplace',
163 default = 800
164 ))
165
166
167 height = int(cfg.get2 (
168 option = 'main.window.height',
169 workplace = gmSurgery.gmCurrentPractice().active_workplace,
170 bias = 'workplace',
171 default = 600
172 ))
173
174 dw = wx.DisplaySize()[0]
175 dh = wx.DisplaySize()[1]
176
177 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
178 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
179 _log.debug('previous GUI size [%s:%s]', width, height)
180
181
182 if width > dw:
183 _log.debug('adjusting GUI width from %s to %s', width, dw)
184 width = dw
185
186 if height > dh:
187 _log.debug('adjusting GUI height from %s to %s', height, dh)
188 height = dh
189
190
191 if width < 100:
192 _log.debug('adjusting GUI width to minimum of 100 pixel')
193 width = 100
194 if height < 100:
195 _log.debug('adjusting GUI height to minimum of 100 pixel')
196 height = 100
197
198 _log.info('setting GUI to size [%s:%s]', width, height)
199
200 self.SetClientSize(wx.Size(width, height))
201
203 """Create the main menu entries.
204
205 Individual entries are farmed out to the modules.
206
207 menu item template:
208
209 item = menu_emr_edit.Append(-1, _(''), _(''))
210 self.Bind(wx.EVT_MENU, self__on_, item)
211 """
212 global wx
213 self.mainmenu = wx.MenuBar()
214 self.__gb['main.mainmenu'] = self.mainmenu
215
216
217 menu_gnumed = wx.Menu()
218
219 self.menu_plugins = wx.Menu()
220 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
221
222 ID = wx.NewId()
223 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
224 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
225
226 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
227 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
228
229
230 menu_gnumed.AppendSeparator()
231
232
233 menu_config = wx.Menu()
234 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
235
236 item = menu_config.Append(-1, _('List configuration'), _('List all configuration items stored in the database.'))
237 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
238
239
240 menu_cfg_db = wx.Menu()
241 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
242
243 ID = wx.NewId()
244 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
245 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
246
247 ID = wx.NewId()
248 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
249 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
250
251
252 menu_cfg_client = wx.Menu()
253 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
254
255 ID = wx.NewId()
256 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
257 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
258
259 ID = wx.NewId()
260 menu_cfg_client.Append(ID, _('Temporary directory'), _('Configure the directory to use as scratch space for temporary files.'))
261 wx.EVT_MENU(self, ID, self.__on_configure_temp_dir)
262
263 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
264 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
265
266
267 menu_cfg_ui = wx.Menu()
268 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
269
270
271 menu_cfg_doc = wx.Menu()
272 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
273
274 ID = wx.NewId()
275 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
276 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
277
278 ID = wx.NewId()
279 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
280 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
281
282 ID = wx.NewId()
283 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
284 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
285
286
287 menu_cfg_update = wx.Menu()
288 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
289
290 ID = wx.NewId()
291 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
292 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
293
294 ID = wx.NewId()
295 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
296 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
297
298 ID = wx.NewId()
299 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
300 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
301
302
303 menu_cfg_pat_search = wx.Menu()
304 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
305
306 ID = wx.NewId()
307 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
308 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
309
310 ID = wx.NewId()
311 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
312 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
313
314 ID = wx.NewId()
315 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
316 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
317
318 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
319 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
320
321 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
322 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
323
324
325 menu_cfg_soap_editing = wx.Menu()
326 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
327
328 ID = wx.NewId()
329 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
330 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
331
332
333 menu_cfg_ext_tools = wx.Menu()
334 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
335
336
337
338
339
340 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
341 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
342
343 ID = wx.NewId()
344 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
345 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
346
347 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
348 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
349
350 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
351 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
352
353 item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.'))
354 self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item)
355
356 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
357 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
358
359 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
360 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
361
362 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
363 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
364
365 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
366 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
367
368
369 menu_cfg_emr = wx.Menu()
370 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
371
372 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
373 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
374
375 item = menu_cfg_emr.Append(-1, _('Primary doctor'), _('Select the primary doctor to fall back to for patients without one.'))
376 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
377
378
379 menu_cfg_encounter = wx.Menu()
380 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
381
382 ID = wx.NewId()
383 menu_cfg_encounter.Append(ID, _('Edit on patient change'), _('Edit encounter details on changing of patients.'))
384 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
385
386 ID = wx.NewId()
387 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
388 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
389
390 ID = wx.NewId()
391 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
392 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
393
394 ID = wx.NewId()
395 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
396 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
397
398 ID = wx.NewId()
399 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
400 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
401
402
403 menu_cfg_episode = wx.Menu()
404 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
405
406 ID = wx.NewId()
407 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
408 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
409
410
411 menu_master_data = wx.Menu()
412 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
413
414 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
415 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
416
417 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
418 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
419
420 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
421 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
422
423 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
424 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
425
426
427 menu_users = wx.Menu()
428 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
429
430 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
431 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
432
433 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
434 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
435
436
437 menu_gnumed.AppendSeparator()
438
439 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
440 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
441
442 self.mainmenu.Append(menu_gnumed, '&GNUmed')
443
444
445 menu_patient = wx.Menu()
446
447 ID_CREATE_PATIENT = wx.NewId()
448 menu_patient.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
449 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
450
451 ID_LOAD_EXT_PAT = wx.NewId()
452 menu_patient.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
453 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
454
455 ID_DEL_PAT = wx.NewId()
456 menu_patient.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
457 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
458
459 item = menu_patient.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
460 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
461
462 menu_patient.AppendSeparator()
463
464 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
465 menu_patient.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
466 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
467
468
469 ID = wx.NewId()
470 menu_patient.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
471 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
472
473 menu_patient.AppendSeparator()
474
475 self.mainmenu.Append(menu_patient, '&Person')
476 self.__gb['main.patientmenu'] = menu_patient
477
478
479 menu_emr = wx.Menu()
480 self.mainmenu.Append(menu_emr, _("&EMR"))
481 self.__gb['main.emrmenu'] = menu_emr
482
483
484 menu_emr_show = wx.Menu()
485 menu_emr.AppendMenu(wx.NewId(), _('Show as ...'), menu_emr_show)
486 self.__gb['main.emr_showmenu'] = menu_emr_show
487
488
489 item = menu_emr_show.Append(-1, _('Summary'), _('Show a high-level summary of the EMR.'))
490 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
491
492
493 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
494 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
495
496 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
497 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
498
499
500 menu_emr_edit = wx.Menu()
501 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
502
503 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'))
504 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
505
506 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
507 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
508
509 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
510 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
511
512 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
513 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
514
515 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
516 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
517
518 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
519 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
520
521 item = menu_emr_edit.Append(-1, _('&Measurement(s)'), _('Add (a) measurement result(s) for the current patient.'))
522 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
523
524 item = menu_emr_edit.Append(-1, _('&Vaccination(s)'), _('Add (a) vaccination(s) for the current patient.'))
525 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
526
527
528
529
530 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
531 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
532
533
534 item = menu_emr.Append(-1, _('&Encounters list'), _('List all encounters including empty ones.'))
535 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
536
537
538 menu_emr.AppendSeparator()
539
540 menu_emr_export = wx.Menu()
541 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
542
543 ID_EXPORT_EMR_ASCII = wx.NewId()
544 menu_emr_export.Append (
545 ID_EXPORT_EMR_ASCII,
546 _('Text document'),
547 _("Export the EMR of the active patient into a text file")
548 )
549 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
550
551 ID_EXPORT_EMR_JOURNAL = wx.NewId()
552 menu_emr_export.Append (
553 ID_EXPORT_EMR_JOURNAL,
554 _('Journal'),
555 _("Export the EMR of the active patient as a chronological journal into a text file")
556 )
557 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
558
559 ID_EXPORT_MEDISTAR = wx.NewId()
560 menu_emr_export.Append (
561 ID_EXPORT_MEDISTAR,
562 _('MEDISTAR import format'),
563 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
564 )
565 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
566
567
568 menu_emr.AppendSeparator()
569
570
571 menu_paperwork = wx.Menu()
572
573 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
574 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
575
576 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
577
578
579 self.menu_tools = wx.Menu()
580 self.__gb['main.toolsmenu'] = self.menu_tools
581 self.mainmenu.Append(self.menu_tools, _("&Tools"))
582
583 ID_DICOM_VIEWER = wx.NewId()
584 viewer = _('no viewer installed')
585 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
586 viewer = u'OsiriX'
587 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
588 viewer = u'Aeskulap'
589 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
590 viewer = u'AMIDE'
591 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
592 viewer = u'DicomScope'
593 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
594 viewer = u'(x)medcon'
595 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)
596 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
597 if viewer == _('no viewer installed'):
598 _log.info('neither of OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
599 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
600
601
602
603
604
605 ID = wx.NewId()
606 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
607 wx.EVT_MENU(self, ID, self.__on_snellen)
608
609 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
610 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
611
612 self.menu_tools.AppendSeparator()
613
614
615 menu_knowledge = wx.Menu()
616 self.__gb['main.knowledgemenu'] = menu_knowledge
617 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
618
619 menu_drug_dbs = wx.Menu()
620 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
621
622 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
623 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
624
625
626
627
628
629
630 menu_id = wx.NewId()
631 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
632 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
633
634
635
636
637 ID_MEDICAL_LINKS = wx.NewId()
638 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
639 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
640
641
642 self.menu_office = wx.Menu()
643
644 self.__gb['main.officemenu'] = self.menu_office
645 self.mainmenu.Append(self.menu_office, _('&Office'))
646
647 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
648 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
649
650 self.menu_office.AppendSeparator()
651
652
653 help_menu = wx.Menu()
654
655 ID = wx.NewId()
656 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
657 wx.EVT_MENU(self, ID, self.__on_display_wiki)
658
659 ID = wx.NewId()
660 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
661 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
662
663 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
664 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
665
666 menu_debugging = wx.Menu()
667 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
668
669 ID_SCREENSHOT = wx.NewId()
670 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
671 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
672
673 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
674 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
675
676 ID = wx.NewId()
677 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
678 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
679
680 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
681 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
682
683 ID = wx.NewId()
684 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
685 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
686
687 ID_UNBLOCK = wx.NewId()
688 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
689 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
690
691 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
692 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
693
694
695
696
697 if _cfg.get(option = 'debug'):
698 ID_TOGGLE_PAT_LOCK = wx.NewId()
699 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient'), _('Lock/unlock patient - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
700 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
701
702 ID_TEST_EXCEPTION = wx.NewId()
703 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
704 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
705
706 ID = wx.NewId()
707 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
708 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
709 try:
710 import wx.lib.inspection
711 except ImportError:
712 menu_debugging.Enable(id = ID, enable = False)
713
714 help_menu.AppendSeparator()
715
716 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
717 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
718
719 ID_CONTRIBUTORS = wx.NewId()
720 help_menu.Append(ID_CONTRIBUTORS, _('GNUmed contributors'), _('show GNUmed contributors'))
721 wx.EVT_MENU(self, ID_CONTRIBUTORS, self.__on_show_contributors)
722
723 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
724 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
725
726 help_menu.AppendSeparator()
727
728
729 self.__gb['main.helpmenu'] = help_menu
730 self.mainmenu.Append(help_menu, _("&Help"))
731
732
733
734 self.SetMenuBar(self.mainmenu)
735
738
739
740
742 """register events we want to react to"""
743
744 wx.EVT_CLOSE(self, self.OnClose)
745 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
746 wx.EVT_END_SESSION(self, self._on_end_session)
747
748 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
749 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
750 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
751 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
752 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
753 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
754 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
755 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
756
757 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
758
759 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
760
761 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
762
763 _log.debug('registering plugin with menu system')
764 _log.debug(' generic name: %s', plugin_name)
765 _log.debug(' class name: %s', class_name)
766 _log.debug(' specific menu: %s', menu_name)
767 _log.debug(' menu item: %s', menu_item_name)
768
769
770 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
771 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
772 self.menu_id2plugin[item.Id] = class_name
773
774
775 if menu_name is not None:
776 menu = self.__gb['main.%smenu' % menu_name]
777 item = menu.Append(-1, menu_item_name, menu_help_string)
778 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
779 self.menu_id2plugin[item.Id] = class_name
780
781 return True
782
784 gmDispatcher.send (
785 signal = u'display_widget',
786 name = self.menu_id2plugin[evt.Id]
787 )
788
790 wx.Bell()
791 wx.Bell()
792 wx.Bell()
793 _log.warning('unhandled event detected: QUERY_END_SESSION')
794 _log.info('we should be saving ourselves from here')
795 gmLog2.flush()
796 print "unhandled event detected: QUERY_END_SESSION"
797
799 wx.Bell()
800 wx.Bell()
801 wx.Bell()
802 _log.warning('unhandled event detected: END_SESSION')
803 gmLog2.flush()
804 print "unhandled event detected: END_SESSION"
805
807 if not callable(callback):
808 raise TypeError(u'callback [%s] not callable' % callback)
809
810 self.__pre_exit_callbacks.append(callback)
811
812 - def _on_set_statustext_pubsub(self, context=None):
813 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
814 wx.CallAfter(self.SetStatusText, msg)
815
816 try:
817 if context.data['beep']:
818 wx.Bell()
819 except KeyError:
820 pass
821
822 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
823
824 if msg is None:
825 msg = _('programmer forgot to specify status message')
826
827 if loglevel is not None:
828 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
829
830 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
831 wx.CallAfter(self.SetStatusText, msg)
832
833 if beep:
834 wx.Bell()
835
837 wx.CallAfter(self.__on_db_maintenance_warning)
838
840
841 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
842 wx.Bell()
843 if not wx.GetApp().IsActive():
844 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
845
846 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
847
848 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
849 None,
850 -1,
851 caption = _('Database shutdown warning'),
852 question = _(
853 'The database will be shut down for maintenance\n'
854 'in a few minutes.\n'
855 '\n'
856 'In order to not suffer any loss of data you\n'
857 'will need to save your current work and log\n'
858 'out of this GNUmed client.\n'
859 ),
860 button_defs = [
861 {
862 u'label': _('Close now'),
863 u'tooltip': _('Close this GNUmed client immediately.'),
864 u'default': False
865 },
866 {
867 u'label': _('Finish work'),
868 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
869 u'default': True
870 }
871 ]
872 )
873 decision = dlg.ShowModal()
874 if decision == wx.ID_YES:
875 top_win = wx.GetApp().GetTopWindow()
876 wx.CallAfter(top_win.Close)
877
879 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
880
882
883 if not wx.GetApp().IsActive():
884 if urgent:
885 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
886 else:
887 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
888
889 if msg is not None:
890 self.SetStatusText(msg)
891
892 if urgent:
893 wx.Bell()
894
895 gmHooks.run_hook_script(hook = u'request_user_attention')
896
898 wx.CallAfter(self.__on_pat_name_changed)
899
901 self.__update_window_title()
902
904 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
905
907 self.__update_window_title()
908 try:
909 gmHooks.run_hook_script(hook = u'post_patient_activation')
910 except:
911 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
912 raise
913
915 return self.__sanity_check_encounter()
916
973
974
975
978
986
987
988
1003
1026
1028 from Gnumed.wxpython import gmAbout
1029 contribs = gmAbout.cContributorsDlg (
1030 parent = self,
1031 id = -1,
1032 title = _('GNUmed contributors'),
1033 size = wx.Size(400,600),
1034 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1035 )
1036 contribs.ShowModal()
1037 del contribs
1038 del gmAbout
1039
1040
1041
1043 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1044 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1045 self.Close(True)
1046 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1047
1050
1052 send = gmGuiHelpers.gm_show_question (
1053 _('This will send a notification about database downtime\n'
1054 'to all GNUmed clients connected to your database.\n'
1055 '\n'
1056 'Do you want to send the notification ?\n'
1057 ),
1058 _('Announcing database maintenance downtime')
1059 )
1060 if not send:
1061 return
1062 gmPG2.send_maintenance_notification()
1063
1064
1067
1068
1069
1101
1114
1115 gmCfgWidgets.configure_string_option (
1116 message = _(
1117 'Some network installations cannot cope with loading\n'
1118 'documents of arbitrary size in one piece from the\n'
1119 'database (mainly observed on older Windows versions)\n.'
1120 '\n'
1121 'Under such circumstances documents need to be retrieved\n'
1122 'in chunks and reassembled on the client.\n'
1123 '\n'
1124 'Here you can set the size (in Bytes) above which\n'
1125 'GNUmed will retrieve documents in chunks. Setting this\n'
1126 'value to 0 will disable the chunking protocol.'
1127 ),
1128 option = 'horstspace.blob_export_chunk_size',
1129 bias = 'workplace',
1130 default_value = 1024 * 1024,
1131 validator = is_valid
1132 )
1133
1134
1135
1203
1207
1208
1209
1218
1219 gmCfgWidgets.configure_string_option (
1220 message = _(
1221 'When GNUmed cannot find an OpenOffice server it\n'
1222 'will try to start one. OpenOffice, however, needs\n'
1223 'some time to fully start up.\n'
1224 '\n'
1225 'Here you can set the time for GNUmed to wait for OOo.\n'
1226 ),
1227 option = 'external.ooo.startup_settle_time',
1228 bias = 'workplace',
1229 default_value = 2.0,
1230 validator = is_valid
1231 )
1232
1235
1250
1251 gmCfgWidgets.configure_string_option (
1252 message = _(
1253 'GNUmed will use this URL to access a website which lets\n'
1254 'you report an adverse drug reaction (ADR).\n'
1255 '\n'
1256 'If you leave this empty it will fall back\n'
1257 'to an URL for reporting ADRs in Germany.'
1258 ),
1259 option = 'external.urls.report_ADR',
1260 bias = 'user',
1261 default_value = german_default,
1262 validator = is_valid
1263 )
1264
1278
1279 gmCfgWidgets.configure_string_option (
1280 message = _(
1281 'GNUmed will use this URL to access a website which lets\n'
1282 'you report an adverse vaccination reaction (vADR).\n'
1283 '\n'
1284 'If you set it to a specific address that URL must be\n'
1285 'accessible now. If you leave it empty it will fall back\n'
1286 'to the URL for reporting other adverse drug reactions.'
1287 ),
1288 option = 'external.urls.report_vaccine_ADR',
1289 bias = 'user',
1290 default_value = german_default,
1291 validator = is_valid
1292 )
1293
1307
1308 gmCfgWidgets.configure_string_option (
1309 message = _(
1310 'GNUmed will use this URL to access an encyclopedia of\n'
1311 'measurement/lab methods from within the measurments grid.\n'
1312 '\n'
1313 'You can leave this empty but to set it to a specific\n'
1314 'address the URL must be accessible now.'
1315 ),
1316 option = 'external.urls.measurements_encyclopedia',
1317 bias = 'user',
1318 default_value = german_default,
1319 validator = is_valid
1320 )
1321
1335
1336 gmCfgWidgets.configure_string_option (
1337 message = _(
1338 'GNUmed will use this URL to access a page showing\n'
1339 'vaccination schedules.\n'
1340 '\n'
1341 'You can leave this empty but to set it to a specific\n'
1342 'address the URL must be accessible now.'
1343 ),
1344 option = 'external.urls.vaccination_plans',
1345 bias = 'user',
1346 default_value = german_default,
1347 validator = is_valid
1348 )
1349
1362
1363 gmCfgWidgets.configure_string_option (
1364 message = _(
1365 'Enter the shell command with which to start the\n'
1366 'the ACS risk assessment calculator.\n'
1367 '\n'
1368 'GNUmed will try to verify the path which may,\n'
1369 'however, fail if you are using an emulator such\n'
1370 'as Wine. Nevertheless, starting the calculator\n'
1371 'will work as long as the shell command is correct\n'
1372 'despite the failing test.'
1373 ),
1374 option = 'external.tools.acs_risk_calculator_cmd',
1375 bias = 'user',
1376 validator = is_valid
1377 )
1378
1381
1394
1395 gmCfgWidgets.configure_string_option (
1396 message = _(
1397 'Enter the shell command with which to start\n'
1398 'the FreeDiams drug database frontend.\n'
1399 '\n'
1400 'GNUmed will try to verify that path.'
1401 ),
1402 option = 'external.tools.freediams_cmd',
1403 bias = 'workplace',
1404 default_value = None,
1405 validator = is_valid
1406 )
1407
1420
1421 gmCfgWidgets.configure_string_option (
1422 message = _(
1423 'Enter the shell command with which to start the\n'
1424 'the IFAP drug database.\n'
1425 '\n'
1426 'GNUmed will try to verify the path which may,\n'
1427 'however, fail if you are using an emulator such\n'
1428 'as Wine. Nevertheless, starting IFAP will work\n'
1429 'as long as the shell command is correct despite\n'
1430 'the failing test.'
1431 ),
1432 option = 'external.ifap-win.shell_command',
1433 bias = 'workplace',
1434 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1435 validator = is_valid
1436 )
1437
1438
1439
1488
1489
1490
1507
1510
1513
1518
1519 gmCfgWidgets.configure_string_option (
1520 message = _(
1521 'When a patient is activated GNUmed checks the\n'
1522 "proximity of the patient's birthday.\n"
1523 '\n'
1524 'If the birthday falls within the range of\n'
1525 ' "today %s <the interval you set here>"\n'
1526 'GNUmed will remind you of the recent or\n'
1527 'imminent anniversary.'
1528 ) % u'\u2213',
1529 option = u'patient_search.dob_warn_interval',
1530 bias = 'user',
1531 default_value = '1 week',
1532 validator = is_valid
1533 )
1534
1536
1537 gmCfgWidgets.configure_boolean_option (
1538 parent = self,
1539 question = _(
1540 'When adding progress notes do you want to\n'
1541 'allow opening several unassociated, new\n'
1542 'episodes for a patient at once ?\n'
1543 '\n'
1544 'This can be particularly helpful when entering\n'
1545 'progress notes on entirely new patients presenting\n'
1546 'with a multitude of problems on their first visit.'
1547 ),
1548 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1549 button_tooltips = [
1550 _('Yes, allow for multiple new episodes concurrently.'),
1551 _('No, only allow editing one new episode at a time.')
1552 ]
1553 )
1554
1600
1601
1602
1605
1608
1622
1624 gmCfgWidgets.configure_boolean_option (
1625 parent = self,
1626 question = _(
1627 'Do you want GNUmed to show the encounter\n'
1628 'details editor when changing the active patient ?'
1629 ),
1630 option = 'encounter.show_editor_before_patient_change',
1631 button_tooltips = [
1632 _('Yes, show the encounter editor if it seems appropriate.'),
1633 _('No, never show the encounter editor even if it would seem useful.')
1634 ]
1635 )
1636
1641
1642 gmCfgWidgets.configure_string_option (
1643 message = _(
1644 'When a patient is activated GNUmed checks the\n'
1645 'chart for encounters lacking any entries.\n'
1646 '\n'
1647 'Any such encounters older than what you set\n'
1648 'here will be removed from the medical record.\n'
1649 '\n'
1650 'To effectively disable removal of such encounters\n'
1651 'set this option to an improbable value.\n'
1652 ),
1653 option = 'encounter.ttl_if_empty',
1654 bias = 'user',
1655 default_value = '1 week',
1656 validator = is_valid
1657 )
1658
1663
1664 gmCfgWidgets.configure_string_option (
1665 message = _(
1666 'When a patient is activated GNUmed checks the\n'
1667 'age of the most recent encounter.\n'
1668 '\n'
1669 'If that encounter is younger than this age\n'
1670 'the existing encounter will be continued.\n'
1671 '\n'
1672 '(If it is really old a new encounter is\n'
1673 ' started, or else GNUmed will ask you.)\n'
1674 ),
1675 option = 'encounter.minimum_ttl',
1676 bias = 'user',
1677 default_value = '1 hour 30 minutes',
1678 validator = is_valid
1679 )
1680
1685
1686 gmCfgWidgets.configure_string_option (
1687 message = _(
1688 'When a patient is activated GNUmed checks the\n'
1689 'age of the most recent encounter.\n'
1690 '\n'
1691 'If that encounter is older than this age\n'
1692 'GNUmed will always start a new encounter.\n'
1693 '\n'
1694 '(If it is very recent the existing encounter\n'
1695 ' is continued, or else GNUmed will ask you.)\n'
1696 ),
1697 option = 'encounter.maximum_ttl',
1698 bias = 'user',
1699 default_value = '6 hours',
1700 validator = is_valid
1701 )
1702
1711
1712 gmCfgWidgets.configure_string_option (
1713 message = _(
1714 'At any time there can only be one open (ongoing)\n'
1715 'episode for each health issue.\n'
1716 '\n'
1717 'When you try to open (add data to) an episode on a health\n'
1718 'issue GNUmed will check for an existing open episode on\n'
1719 'that issue. If there is any it will check the age of that\n'
1720 'episode. The episode is closed if it has been dormant (no\n'
1721 'data added, that is) for the period of time (in days) you\n'
1722 'set here.\n'
1723 '\n'
1724 "If the existing episode hasn't been dormant long enough\n"
1725 'GNUmed will consult you what to do.\n'
1726 '\n'
1727 'Enter maximum episode dormancy in DAYS:'
1728 ),
1729 option = 'episode.ttl',
1730 bias = 'user',
1731 default_value = 60,
1732 validator = is_valid
1733 )
1734
1765
1780
1805
1817
1818 gmCfgWidgets.configure_string_option (
1819 message = _(
1820 'GNUmed can check for new releases being available. To do\n'
1821 'so it needs to load version information from an URL.\n'
1822 '\n'
1823 'The default URL is:\n'
1824 '\n'
1825 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1826 '\n'
1827 'but you can configure any other URL locally. Note\n'
1828 'that you must enter the location as a valid URL.\n'
1829 'Depending on the URL the client will need online\n'
1830 'access when checking for updates.'
1831 ),
1832 option = u'horstspace.update.url',
1833 bias = u'workplace',
1834 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1835 validator = is_valid
1836 )
1837
1855
1872
1883
1884 gmCfgWidgets.configure_string_option (
1885 message = _(
1886 'GNUmed can show the document review dialog after\n'
1887 'calling the appropriate viewer for that document.\n'
1888 '\n'
1889 'Select the conditions under which you want\n'
1890 'GNUmed to do so:\n'
1891 '\n'
1892 ' 0: never display the review dialog\n'
1893 ' 1: always display the dialog\n'
1894 ' 2: only if there is no previous review by me\n'
1895 '\n'
1896 'Note that if a viewer is configured to not block\n'
1897 'GNUmed during document display the review dialog\n'
1898 'will actually appear in parallel to the viewer.'
1899 ),
1900 option = u'horstspace.document_viewer.review_after_display',
1901 bias = u'user',
1902 default_value = 2,
1903 validator = is_valid
1904 )
1905
1907
1908 master_data_lists = [
1909 'adr',
1910 'drugs',
1911 'codes',
1912 'labs',
1913 'substances_in_brands',
1914 'form_templates',
1915 'doc_types',
1916 'enc_types',
1917 'text_expansions',
1918 'meta_test_types',
1919 'orgs',
1920 'provinces',
1921 'db_translations',
1922 'substances',
1923 'test_types',
1924 'org_units',
1925 'vacc_indications',
1926 'vaccines',
1927 'workplaces'
1928 ]
1929
1930 master_data_list_names = {
1931 'adr': _('Addresses (likely slow)'),
1932 'drugs': _('Branded drugs (as marketed)'),
1933 'codes': _('Codes and their respective terms'),
1934 'labs': _('Diagnostic organizations (path labs, ...)'),
1935 'substances_in_brands': _('Components of (substances in) branded drugs'),
1936 'form_templates': _('Document templates (forms, letters, plots, ...)'),
1937 'doc_types': _('Document types'),
1938 'enc_types': _('Encounter types'),
1939 'text_expansions': _('Keyword based text expansion macros'),
1940 'meta_test_types': _('Meta test/measurement types'),
1941 'orgs': _('Organizations'),
1942 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
1943 'db_translations': _('String translations in the database'),
1944 'substances': _('Substances in use (taken by patients)'),
1945 'test_types': _('Test/measurement types'),
1946 'org_units': _('Units of organizations (branches, sites, departments, parts, ...'),
1947 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
1948 'vaccines': _('Vaccines'),
1949 'workplaces': _('Workplace profiles (which plugins to load)')
1950 }
1951
1952 map_list2handler = {
1953 'org_units': gmOrganizationWidgets.manage_org_units,
1954 'form_templates': gmFormWidgets.manage_form_templates,
1955 'doc_types': gmDocumentWidgets.manage_document_types,
1956 'text_expansions': gmProviderInboxWidgets.configure_keyword_text_expansion,
1957 'db_translations': gmI18nWidgets.manage_translations,
1958 'codes': gmCodingWidgets.browse_coded_terms,
1959 'enc_types': gmEMRStructWidgets.manage_encounter_types,
1960 'provinces': gmPersonContactWidgets.manage_provinces,
1961 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins,
1962 'substances': gmMedicationWidgets.manage_substances_in_use,
1963 'drugs': gmMedicationWidgets.manage_branded_drugs,
1964 'substances_in_brands': gmMedicationWidgets.manage_substances_in_brands,
1965 'labs': gmMeasurementWidgets.manage_measurement_orgs,
1966 'test_types': gmMeasurementWidgets.manage_measurement_types,
1967 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
1968 'vaccines': gmVaccWidgets.manage_vaccines,
1969 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
1970 'orgs': gmOrganizationWidgets.manage_orgs,
1971 'adr': gmPersonContactWidgets.manage_addresses
1972 }
1973
1974
1975 def edit(item):
1976 try: map_list2handler[item](parent = self)
1977 except KeyError: pass
1978 return False
1979
1980
1981 gmListWidgets.get_choices_from_list (
1982 parent = self,
1983 caption = _('Master data management'),
1984 choices = [ master_data_list_names[lst] for lst in master_data_lists],
1985 data = master_data_lists,
1986 columns = [_('Select the list you want to manage:')],
1987 edit_callback = edit,
1988 single_selection = True,
1989 ignore_OK_button = True
1990 )
1991
2005
2007
2008 dbcfg = gmCfg.cCfgSQL()
2009 cmd = dbcfg.get2 (
2010 option = u'external.tools.acs_risk_calculator_cmd',
2011 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2012 bias = 'user'
2013 )
2014
2015 if cmd is None:
2016 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2017 return
2018
2019 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
2020 try:
2021 subprocess.check_call (
2022 args = (cmd,),
2023 close_fds = True,
2024 cwd = cwd
2025 )
2026 except (OSError, ValueError, subprocess.CalledProcessError):
2027 _log.exception('there was a problem executing [%s]', cmd)
2028 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2029 return
2030
2031 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2032 for pdf in pdfs:
2033 try:
2034 open(pdf).close()
2035 except:
2036 _log.exception('error accessing [%s]', pdf)
2037 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the ARRIBA result in [%s] !') % pdf, beep = True)
2038 continue
2039
2040 doc = gmDocumentWidgets.save_file_as_new_document (
2041 parent = self,
2042 filename = pdf,
2043 document_type = u'risk assessment'
2044 )
2045
2046 try:
2047 os.remove(pdf)
2048 except StandardError:
2049 _log.exception('cannot remove [%s]', pdf)
2050
2051 if doc is None:
2052 continue
2053 doc['comment'] = u'ARRIBA: %s' % _('cardiovascular risk assessment')
2054 doc.save()
2055
2056 return
2057
2059 dlg = gmSnellen.cSnellenCfgDlg()
2060 if dlg.ShowModal() != wx.ID_OK:
2061 return
2062
2063 frame = gmSnellen.cSnellenChart (
2064 width = dlg.vals[0],
2065 height = dlg.vals[1],
2066 alpha = dlg.vals[2],
2067 mirr = dlg.vals[3],
2068 parent = None
2069 )
2070 frame.CentreOnScreen(wx.BOTH)
2071
2072
2073 frame.Show(True)
2074
2075
2077 webbrowser.open (
2078 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
2079 new = False,
2080 autoraise = True
2081 )
2082
2085
2087 webbrowser.open (
2088 url = 'http://www.kompendium.ch',
2089 new = False,
2090 autoraise = True
2091 )
2092
2093
2094
2098
2099
2100
2102 wx.CallAfter(self.__save_screenshot)
2103 evt.Skip()
2104
2106
2107 time.sleep(0.5)
2108
2109 rect = self.GetRect()
2110
2111
2112 if sys.platform == 'linux2':
2113 client_x, client_y = self.ClientToScreen((0, 0))
2114 border_width = client_x - rect.x
2115 title_bar_height = client_y - rect.y
2116
2117 if self.GetMenuBar():
2118 title_bar_height /= 2
2119 rect.width += (border_width * 2)
2120 rect.height += title_bar_height + border_width
2121
2122 wdc = wx.ScreenDC()
2123 mdc = wx.MemoryDC()
2124 img = wx.EmptyBitmap(rect.width, rect.height)
2125 mdc.SelectObject(img)
2126 mdc.Blit (
2127 0, 0,
2128 rect.width, rect.height,
2129 wdc,
2130 rect.x, rect.y
2131 )
2132
2133
2134 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2135 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2136 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2137
2139
2140 raise ValueError('raised ValueError to test exception handling')
2141
2143 import wx.lib.inspection
2144 wx.lib.inspection.InspectionTool().Show()
2145
2147 webbrowser.open (
2148 url = 'https://bugs.launchpad.net/gnumed/',
2149 new = False,
2150 autoraise = True
2151 )
2152
2154 webbrowser.open (
2155 url = 'http://wiki.gnumed.de',
2156 new = False,
2157 autoraise = True
2158 )
2159
2161 webbrowser.open (
2162 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
2163 new = False,
2164 autoraise = True
2165 )
2166
2168 webbrowser.open (
2169 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
2170 new = False,
2171 autoraise = True
2172 )
2173
2180
2184
2187
2194
2199
2201 name = os.path.basename(gmLog2._logfile_name)
2202 name, ext = os.path.splitext(name)
2203 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2204 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2205
2206 dlg = wx.FileDialog (
2207 parent = self,
2208 message = _("Save current log as..."),
2209 defaultDir = new_path,
2210 defaultFile = new_name,
2211 wildcard = "%s (*.log)|*.log" % _("log files"),
2212 style = wx.SAVE
2213 )
2214 choice = dlg.ShowModal()
2215 new_name = dlg.GetPath()
2216 dlg.Destroy()
2217 if choice != wx.ID_OK:
2218 return True
2219
2220 _log.warning('syncing log file for backup to [%s]', new_name)
2221 gmLog2.flush()
2222 shutil.copy2(gmLog2._logfile_name, new_name)
2223 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2224
2227
2228
2229
2231 """This is the wx.EVT_CLOSE handler.
2232
2233 - framework still functional
2234 """
2235 _log.debug('gmTopLevelFrame.OnClose() start')
2236 self._clean_exit()
2237 self.Destroy()
2238 _log.debug('gmTopLevelFrame.OnClose() end')
2239 return True
2240
2246
2251
2259
2266
2273
2283
2291
2299
2307
2315
2324
2332
2334 pat = gmPerson.gmCurrentPatient()
2335 if not pat.connected:
2336 gmDispatcher.send(signal = 'statustext', msg = _('Cannot show EMR summary. No active patient.'))
2337 return False
2338
2339 emr = pat.get_emr()
2340 dlg = wx.MessageDialog (
2341 parent = self,
2342 message = emr.format_statistics(),
2343 caption = _('EMR Summary'),
2344 style = wx.OK | wx.STAY_ON_TOP
2345 )
2346 dlg.ShowModal()
2347 dlg.Destroy()
2348 return True
2349
2352
2355
2357
2358 pat = gmPerson.gmCurrentPatient()
2359 if not pat.connected:
2360 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2361 return False
2362
2363 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2364
2365 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2366 gmTools.mkdir(aDefDir)
2367
2368 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2369 dlg = wx.FileDialog (
2370 parent = self,
2371 message = _("Save patient's EMR journal as..."),
2372 defaultDir = aDefDir,
2373 defaultFile = fname,
2374 wildcard = aWildcard,
2375 style = wx.SAVE
2376 )
2377 choice = dlg.ShowModal()
2378 fname = dlg.GetPath()
2379 dlg.Destroy()
2380 if choice != wx.ID_OK:
2381 return True
2382
2383 _log.debug('exporting EMR journal to [%s]' % fname)
2384
2385 exporter = gmPatientExporter.cEMRJournalExporter()
2386
2387 wx.BeginBusyCursor()
2388 try:
2389 fname = exporter.export_to_file(filename = fname)
2390 except:
2391 wx.EndBusyCursor()
2392 gmGuiHelpers.gm_show_error (
2393 _('Error exporting patient EMR as chronological journal.'),
2394 _('EMR journal export')
2395 )
2396 raise
2397 wx.EndBusyCursor()
2398
2399 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2400
2401 return True
2402
2409
2419
2421 curr_pat = gmPerson.gmCurrentPatient()
2422 if not curr_pat.connected:
2423 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2424 return False
2425
2426 enc = 'cp850'
2427 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2428 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2429 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2430
2433
2441
2449
2452
2459
2463
2466
2469
2474
2476 """Cleanup helper.
2477
2478 - should ALWAYS be called when this program is
2479 to be terminated
2480 - ANY code that should be executed before a
2481 regular shutdown should go in here
2482 - framework still functional
2483 """
2484 _log.debug('gmTopLevelFrame._clean_exit() start')
2485
2486
2487 listener = gmBackendListener.gmBackendListener()
2488 try:
2489 listener.shutdown()
2490 except:
2491 _log.exception('cannot stop backend notifications listener thread')
2492
2493
2494 if _scripting_listener is not None:
2495 try:
2496 _scripting_listener.shutdown()
2497 except:
2498 _log.exception('cannot stop scripting listener thread')
2499
2500
2501 self.clock_update_timer.Stop()
2502 gmTimer.shutdown()
2503 gmPhraseWheel.shutdown()
2504
2505
2506 for call_back in self.__pre_exit_callbacks:
2507 try:
2508 call_back()
2509 except:
2510 print "*** pre-exit callback failed ***"
2511 print call_back
2512 _log.exception('callback [%s] failed', call_back)
2513
2514
2515 gmDispatcher.send(u'application_closing')
2516
2517
2518 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2519
2520
2521 curr_width, curr_height = self.GetClientSizeTuple()
2522 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2523 dbcfg = gmCfg.cCfgSQL()
2524 dbcfg.set (
2525 option = 'main.window.width',
2526 value = curr_width,
2527 workplace = gmSurgery.gmCurrentPractice().active_workplace
2528 )
2529 dbcfg.set (
2530 option = 'main.window.height',
2531 value = curr_height,
2532 workplace = gmSurgery.gmCurrentPractice().active_workplace
2533 )
2534
2535 if _cfg.get(option = 'debug'):
2536 print '---=== GNUmed shutdown ===---'
2537 try:
2538 print _('You have to manually close this window to finalize shutting down GNUmed.')
2539 print _('This is so that you can inspect the console output at your leisure.')
2540 except UnicodeEncodeError:
2541 print 'You have to manually close this window to finalize shutting down GNUmed.'
2542 print 'This is so that you can inspect the console output at your leisure.'
2543 print '---=== GNUmed shutdown ===---'
2544
2545
2546 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2547
2548
2549 import threading
2550 _log.debug("%s active threads", threading.activeCount())
2551 for t in threading.enumerate():
2552 _log.debug('thread %s', t)
2553
2554 _log.debug('gmTopLevelFrame._clean_exit() end')
2555
2556
2557
2559
2560 if _cfg.get(option = 'slave'):
2561 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2562 _cfg.get(option = 'slave personality'),
2563 _cfg.get(option = 'xml-rpc port')
2564 )
2565 else:
2566 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2567
2569 """Update title of main window based on template.
2570
2571 This gives nice tooltips on iconified GNUmed instances.
2572
2573 User research indicates that in the title bar people want
2574 the date of birth, not the age, so please stick to this
2575 convention.
2576 """
2577 args = {}
2578
2579 pat = gmPerson.gmCurrentPatient()
2580 if pat.connected:
2581 args['pat'] = u'%s %s %s (%s) #%d' % (
2582 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2583 pat['firstnames'],
2584 pat['lastnames'],
2585 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2586 pat['pk_identity']
2587 )
2588 else:
2589 args['pat'] = _('no patient')
2590
2591 args['prov'] = u'%s%s.%s' % (
2592 gmTools.coalesce(_provider['title'], u'', u'%s '),
2593 _provider['firstnames'][:1],
2594 _provider['lastnames']
2595 )
2596
2597 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2598
2599 self.SetTitle(self.__title_template % args)
2600
2601
2603 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2604 sb.SetStatusWidths([-1, 225])
2605
2606 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2607 self._cb_update_clock()
2608
2609 self.clock_update_timer.Start(milliseconds = 1000)
2610
2612 """Displays date and local time in the second slot of the status bar"""
2613 t = time.localtime(time.time())
2614 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2615 self.SetStatusText(st,1)
2616
2618 """Lock GNUmed client against unauthorized access"""
2619
2620
2621
2622 return
2623
2625 """Unlock the main notebook widgets
2626 As long as we are not logged into the database backend,
2627 all pages but the 'login' page of the main notebook widget
2628 are locked; i.e. not accessible by the user
2629 """
2630
2631
2632
2633
2634
2635 return
2636
2638 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2639
2641
2643
2644 self.__starting_up = True
2645
2646 gmExceptionHandlingWidgets.install_wx_exception_handler()
2647 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2648
2649
2650
2651
2652 self.SetAppName(u'gnumed')
2653 self.SetVendorName(u'The GNUmed Development Community.')
2654 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2655 paths.init_paths(wx = wx, app_name = u'gnumed')
2656
2657 if not self.__setup_prefs_file():
2658 return False
2659
2660 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2661
2662 self.__guibroker = gmGuiBroker.GuiBroker()
2663 self.__setup_platform()
2664
2665 if not self.__establish_backend_connection():
2666 return False
2667
2668 if not _cfg.get(option = 'skip-update-check'):
2669 self.__check_for_updates()
2670
2671 if _cfg.get(option = 'slave'):
2672 if not self.__setup_scripting_listener():
2673 return False
2674
2675
2676 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2677 frame.CentreOnScreen(wx.BOTH)
2678 self.SetTopWindow(frame)
2679 frame.Show(True)
2680
2681 if _cfg.get(option = 'debug'):
2682 self.RedirectStdio()
2683 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2684
2685
2686 print '---=== GNUmed startup ===---'
2687 print _('redirecting STDOUT/STDERR to this log window')
2688 print '---=== GNUmed startup ===---'
2689
2690 self.__setup_user_activity_timer()
2691 self.__register_events()
2692
2693 wx.CallAfter(self._do_after_init)
2694
2695 return True
2696
2698 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2699
2700 - after destroying all application windows and controls
2701 - before wx.Windows internal cleanup
2702 """
2703 _log.debug('gmApp.OnExit() start')
2704
2705 self.__shutdown_user_activity_timer()
2706
2707 if _cfg.get(option = 'debug'):
2708 self.RestoreStdio()
2709 sys.stdin = sys.__stdin__
2710 sys.stdout = sys.__stdout__
2711 sys.stderr = sys.__stderr__
2712
2713 _log.debug('gmApp.OnExit() end')
2714
2716 wx.Bell()
2717 wx.Bell()
2718 wx.Bell()
2719 _log.warning('unhandled event detected: QUERY_END_SESSION')
2720 _log.info('we should be saving ourselves from here')
2721 gmLog2.flush()
2722 print "unhandled event detected: QUERY_END_SESSION"
2723
2725 wx.Bell()
2726 wx.Bell()
2727 wx.Bell()
2728 _log.warning('unhandled event detected: END_SESSION')
2729 gmLog2.flush()
2730 print "unhandled event detected: END_SESSION"
2731
2742
2744 self.user_activity_detected = True
2745 evt.Skip()
2746
2748
2749 if self.user_activity_detected:
2750 self.elapsed_inactivity_slices = 0
2751 self.user_activity_detected = False
2752 self.elapsed_inactivity_slices += 1
2753 else:
2754 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2755
2756 pass
2757
2758 self.user_activity_timer.Start(oneShot = True)
2759
2760
2761
2763 try:
2764 kwargs['originated_in_database']
2765 print '==> got notification from database "%s":' % kwargs['signal']
2766 except KeyError:
2767 print '==> received signal from client: "%s"' % kwargs['signal']
2768
2769 del kwargs['signal']
2770 for key in kwargs.keys():
2771 print ' [%s]: %s' % (key, kwargs[key])
2772
2774 print "wx.lib.pubsub message:"
2775 print msg.topic
2776 print msg.data
2777
2783
2785 self.user_activity_detected = True
2786 self.elapsed_inactivity_slices = 0
2787
2788 self.max_user_inactivity_slices = 15
2789 self.user_activity_timer = gmTimer.cTimer (
2790 callback = self._on_user_activity_timer_expired,
2791 delay = 2000
2792 )
2793 self.user_activity_timer.Start(oneShot=True)
2794
2796 try:
2797 self.user_activity_timer.Stop()
2798 del self.user_activity_timer
2799 except:
2800 pass
2801
2803 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2804 wx.EVT_END_SESSION(self, self._on_end_session)
2805
2806
2807
2808
2809
2810 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2811
2812 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2813 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2839
2841 """Handle all the database related tasks necessary for startup."""
2842
2843
2844 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
2845
2846 from Gnumed.wxpython import gmAuthWidgets
2847 connected = gmAuthWidgets.connect_to_database (
2848 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
2849 require_version = not override
2850 )
2851 if not connected:
2852 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
2853 return False
2854
2855
2856 try:
2857 global _provider
2858 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
2859 except ValueError:
2860 account = gmPG2.get_current_user()
2861 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
2862 msg = _(
2863 'The database account [%s] cannot be used as a\n'
2864 'staff member login for GNUmed. There was an\n'
2865 'error retrieving staff details for it.\n\n'
2866 'Please ask your administrator for help.\n'
2867 ) % account
2868 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
2869 return False
2870
2871
2872 tmp = '%s%s %s (%s = %s)' % (
2873 gmTools.coalesce(_provider['title'], ''),
2874 _provider['firstnames'],
2875 _provider['lastnames'],
2876 _provider['short_alias'],
2877 _provider['db_user']
2878 )
2879 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
2880
2881
2882 surgery = gmSurgery.gmCurrentPractice()
2883 msg = surgery.db_logon_banner
2884 if msg.strip() != u'':
2885
2886 login = gmPG2.get_default_login()
2887 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
2888 login.database,
2889 gmTools.coalesce(login.host, u'localhost')
2890 ))
2891 msg = auth + msg + u'\n\n'
2892
2893 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
2894 None,
2895 -1,
2896 caption = _('Verifying database'),
2897 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
2898 button_defs = [
2899 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
2900 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
2901 ]
2902 )
2903 go_on = dlg.ShowModal()
2904 dlg.Destroy()
2905 if go_on != wx.ID_YES:
2906 _log.info('user decided to not connect to this database')
2907 return False
2908
2909
2910 self.__check_db_lang()
2911
2912 return True
2913
2915 """Setup access to a config file for storing preferences."""
2916
2917 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2918
2919 candidates = []
2920 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
2921 if explicit_file is not None:
2922 candidates.append(explicit_file)
2923
2924 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
2925 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
2926 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
2927
2928 prefs_file = None
2929 for candidate in candidates:
2930 try:
2931 open(candidate, 'a+').close()
2932 prefs_file = candidate
2933 break
2934 except IOError:
2935 continue
2936
2937 if prefs_file is None:
2938 msg = _(
2939 'Cannot find configuration file in any of:\n'
2940 '\n'
2941 ' %s\n'
2942 'You may need to use the comand line option\n'
2943 '\n'
2944 ' --conf-file=<FILE>'
2945 ) % '\n '.join(candidates)
2946 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
2947 return False
2948
2949 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
2950 _log.info('user preferences file: %s', prefs_file)
2951
2952 return True
2953
2955
2956 from socket import error as SocketError
2957 from Gnumed.pycommon import gmScriptingListener
2958 from Gnumed.wxpython import gmMacro
2959
2960 slave_personality = gmTools.coalesce (
2961 _cfg.get (
2962 group = u'workplace',
2963 option = u'slave personality',
2964 source_order = [
2965 ('explicit', 'return'),
2966 ('workbase', 'return'),
2967 ('user', 'return'),
2968 ('system', 'return')
2969 ]
2970 ),
2971 u'gnumed-client'
2972 )
2973 _cfg.set_option(option = 'slave personality', value = slave_personality)
2974
2975
2976 port = int (
2977 gmTools.coalesce (
2978 _cfg.get (
2979 group = u'workplace',
2980 option = u'xml-rpc port',
2981 source_order = [
2982 ('explicit', 'return'),
2983 ('workbase', 'return'),
2984 ('user', 'return'),
2985 ('system', 'return')
2986 ]
2987 ),
2988 9999
2989 )
2990 )
2991 _cfg.set_option(option = 'xml-rpc port', value = port)
2992
2993 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
2994 global _scripting_listener
2995 try:
2996 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
2997 except SocketError, e:
2998 _log.exception('cannot start GNUmed XML-RPC server')
2999 gmGuiHelpers.gm_show_error (
3000 aMessage = (
3001 'Cannot start the GNUmed server:\n'
3002 '\n'
3003 ' [%s]'
3004 ) % e,
3005 aTitle = _('GNUmed startup')
3006 )
3007 return False
3008
3009 return True
3010
3031
3033 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3034 _log.warning("system locale is undefined (probably meaning 'C')")
3035 return True
3036
3037
3038 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3039 db_lang = rows[0]['lang']
3040
3041 if db_lang is None:
3042 _log.debug("database locale currently not set")
3043 msg = _(
3044 "There is no language selected in the database for user [%s].\n"
3045 "Your system language is currently set to [%s].\n\n"
3046 "Do you want to set the database language to '%s' ?\n\n"
3047 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3048 checkbox_msg = _('Remember to ignore missing language')
3049 else:
3050 _log.debug("current database locale: [%s]" % db_lang)
3051 msg = _(
3052 "The currently selected database language ('%s') does\n"
3053 "not match the current system language ('%s').\n"
3054 "\n"
3055 "Do you want to set the database language to '%s' ?\n"
3056 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3057 checkbox_msg = _('Remember to ignore language mismatch')
3058
3059
3060 if db_lang == gmI18N.system_locale_level['full']:
3061 _log.debug('Database locale (%s) up to date.' % db_lang)
3062 return True
3063 if db_lang == gmI18N.system_locale_level['country']:
3064 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3065 return True
3066 if db_lang == gmI18N.system_locale_level['language']:
3067 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3068 return True
3069
3070 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3071
3072
3073 ignored_sys_lang = _cfg.get (
3074 group = u'backend',
3075 option = u'ignored mismatching system locale',
3076 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3077 )
3078
3079
3080 if gmI18N.system_locale == ignored_sys_lang:
3081 _log.info('configured to ignore system-to-database locale mismatch')
3082 return True
3083
3084
3085 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3086 None,
3087 -1,
3088 caption = _('Checking database language settings'),
3089 question = msg,
3090 button_defs = [
3091 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3092 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3093 ],
3094 show_checkbox = True,
3095 checkbox_msg = checkbox_msg,
3096 checkbox_tooltip = _(
3097 'Checking this will make GNUmed remember your decision\n'
3098 'until the system language is changed.\n'
3099 '\n'
3100 'You can also reactivate this inquiry by removing the\n'
3101 'corresponding "ignore" option from the configuration file\n'
3102 '\n'
3103 ' [%s]'
3104 ) % _cfg.get(option = 'user_preferences_file')
3105 )
3106 decision = dlg.ShowModal()
3107 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3108 dlg.Destroy()
3109
3110 if decision == wx.ID_NO:
3111 if not remember_ignoring_problem:
3112 return True
3113 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3114 gmCfg2.set_option_in_INI_file (
3115 filename = _cfg.get(option = 'user_preferences_file'),
3116 group = 'backend',
3117 option = 'ignored mismatching system locale',
3118 value = gmI18N.system_locale
3119 )
3120 return True
3121
3122
3123 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3124 if len(lang) > 0:
3125
3126
3127 rows, idx = gmPG2.run_rw_queries (
3128 link_obj = None,
3129 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3130 return_data = True
3131 )
3132 if rows[0][0]:
3133 _log.debug("Successfully set database language to [%s]." % lang)
3134 else:
3135 _log.error('Cannot set database language to [%s].' % lang)
3136 continue
3137 return True
3138
3139
3140 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3141 gmPG2.run_rw_queries(queries = [{
3142 'cmd': u'select i18n.force_curr_lang(%s)',
3143 'args': [gmI18N.system_locale_level['country']]
3144 }])
3145
3146 return True
3147
3149 try:
3150 kwargs['originated_in_database']
3151 print '==> got notification from database "%s":' % kwargs['signal']
3152 except KeyError:
3153 print '==> received signal from client: "%s"' % kwargs['signal']
3154
3155 del kwargs['signal']
3156 for key in kwargs.keys():
3157
3158 try: print ' [%s]: %s' % (key, kwargs[key])
3159 except: print 'cannot print signal information'
3160
3162
3163 try:
3164 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3165 print ' data: %s' % msg.data
3166 print msg
3167 except: print 'problem printing pubsub message information'
3168
3170
3171 if _cfg.get(option = 'debug'):
3172 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3173 _log.debug('gmDispatcher signal monitor activated')
3174 wx.lib.pubsub.Publisher().subscribe (
3175 listener = _signal_debugging_monitor_pubsub,
3176 topic = wx.lib.pubsub.getStrAllTopics()
3177 )
3178 _log.debug('wx.lib.pubsub signal monitor activated')
3179
3180 wx.InitAllImageHandlers()
3181
3182
3183
3184 app = gmApp(redirect = False, clearSigInt = False)
3185 app.MainLoop()
3186
3187
3188
3189 if __name__ == '__main__':
3190
3191 from GNUmed.pycommon import gmI18N
3192 gmI18N.activate_locale()
3193 gmI18N.install_domain()
3194
3195 _log.info('Starting up as main module.')
3196 main()
3197
3198
3199