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 from Gnumed.business import gmArriba
61
62 from Gnumed.exporters import gmPatientExporter
63
64 from Gnumed.wxpython import gmGuiHelpers, gmHorstSpace, gmEMRBrowser
65 from Gnumed.wxpython import gmDemographicsWidgets, gmEMRStructWidgets
66 from Gnumed.wxpython import gmPatSearchWidgets, gmAllergyWidgets, gmListWidgets
67 from Gnumed.wxpython import gmProviderInboxWidgets, gmCfgWidgets, gmExceptionHandlingWidgets
68 from Gnumed.wxpython import gmNarrativeWidgets, gmPhraseWheel, gmMedicationWidgets
69 from Gnumed.wxpython import gmStaffWidgets, gmDocumentWidgets, gmTimer, gmMeasurementWidgets
70 from Gnumed.wxpython import gmFormWidgets, gmSnellen, gmVaccWidgets, gmPersonContactWidgets
71 from Gnumed.wxpython import gmI18nWidgets, gmCodingWidgets
72 from Gnumed.wxpython import gmOrganizationWidgets
73 from Gnumed.wxpython import gmAuthWidgets
74
75
76 try:
77 _('dummy-no-need-to-translate-but-make-epydoc-happy')
78 except NameError:
79 _ = lambda x:x
80
81 _cfg = gmCfg2.gmCfgData()
82 _provider = None
83 _scripting_listener = None
84
85 _log = logging.getLogger('gm.main')
86 _log.info(__version__)
87 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
88
89
91 """GNUmed client's main windows frame.
92
93 This is where it all happens. Avoid popping up any other windows.
94 Most user interaction should happen to and from widgets within this frame
95 """
96
97 - def __init__(self, parent, id, title, size=wx.DefaultSize):
98 """You'll have to browse the source to understand what the constructor does
99 """
100 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
101
102 if wx.Platform == '__WXMSW__':
103 font = self.GetFont()
104 _log.debug('default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
105 desired_font_face = u'DejaVu Sans'
106 success = font.SetFaceName(desired_font_face)
107 if success:
108 self.SetFont(font)
109 _log.debug('setting font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
110 else:
111 font = self.GetFont()
112 _log.error('cannot set font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), desired_font_face)
113
114 self.__gb = gmGuiBroker.GuiBroker()
115 self.__pre_exit_callbacks = []
116 self.bar_width = -1
117 self.menu_id2plugin = {}
118
119 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
120
121 self.__setup_main_menu()
122 self.setup_statusbar()
123 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
124 gmTools.coalesce(_provider['title'], ''),
125 _provider['firstnames'][:1],
126 _provider['lastnames'],
127 _provider['short_alias'],
128 _provider['db_user']
129 ))
130
131 self.__set_window_title_template()
132 self.__update_window_title()
133
134
135
136
137
138 self.SetIcon(gmTools.get_icon(wx = wx))
139
140 self.__register_events()
141
142 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
143 self.vbox = wx.BoxSizer(wx.VERTICAL)
144 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
145
146 self.SetAutoLayout(True)
147 self.SetSizerAndFit(self.vbox)
148
149
150
151
152
153 self.__set_GUI_size()
154
156 """Try to get previous window size from backend."""
157
158 cfg = gmCfg.cCfgSQL()
159
160
161 width = int(cfg.get2 (
162 option = 'main.window.width',
163 workplace = gmSurgery.gmCurrentPractice().active_workplace,
164 bias = 'workplace',
165 default = 800
166 ))
167
168
169 height = int(cfg.get2 (
170 option = 'main.window.height',
171 workplace = gmSurgery.gmCurrentPractice().active_workplace,
172 bias = 'workplace',
173 default = 600
174 ))
175
176 dw = wx.DisplaySize()[0]
177 dh = wx.DisplaySize()[1]
178
179 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
180 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
181 _log.debug('previous GUI size [%s:%s]', width, height)
182
183
184 if width > dw:
185 _log.debug('adjusting GUI width from %s to %s', width, dw)
186 width = dw
187
188 if height > dh:
189 _log.debug('adjusting GUI height from %s to %s', height, dh)
190 height = dh
191
192
193 if width < 100:
194 _log.debug('adjusting GUI width to minimum of 100 pixel')
195 width = 100
196 if height < 100:
197 _log.debug('adjusting GUI height to minimum of 100 pixel')
198 height = 100
199
200 _log.info('setting GUI to size [%s:%s]', width, height)
201
202 self.SetClientSize(wx.Size(width, height))
203
205 """Create the main menu entries.
206
207 Individual entries are farmed out to the modules.
208
209 menu item template:
210
211 item = menu_emr_edit.Append(-1, _(''), _(''))
212 self.Bind(wx.EVT_MENU, self__on_, item)
213 """
214 global wx
215 self.mainmenu = wx.MenuBar()
216 self.__gb['main.mainmenu'] = self.mainmenu
217
218
219 menu_gnumed = wx.Menu()
220
221 self.menu_plugins = wx.Menu()
222 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
223
224 ID = wx.NewId()
225 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
226 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
227
228 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
229 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
230
231
232 menu_gnumed.AppendSeparator()
233
234
235 menu_config = wx.Menu()
236
237 item = menu_config.Append(-1, _('List configuration'), _('List all configuration items stored in the database.'))
238 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
239
240
241 menu_cfg_db = wx.Menu()
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 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
252
253
254 menu_cfg_client = wx.Menu()
255
256 ID = wx.NewId()
257 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
258 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
259
260
261
262
263
264 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
265 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
266
267 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
268
269
270 menu_cfg_ui = wx.Menu()
271
272
273 menu_cfg_doc = wx.Menu()
274
275 ID = wx.NewId()
276 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
277 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
278
279 ID = wx.NewId()
280 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
281 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
282
283 ID = wx.NewId()
284 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
285 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
286
287 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
288
289
290 menu_cfg_update = wx.Menu()
291
292 ID = wx.NewId()
293 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
294 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
295
296 ID = wx.NewId()
297 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
298 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
299
300 ID = wx.NewId()
301 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
302 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
303
304 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
305
306
307 menu_cfg_pat_search = wx.Menu()
308
309 ID = wx.NewId()
310 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
311 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
312
313 ID = wx.NewId()
314 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
315 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
316
317 ID = wx.NewId()
318 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
319 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
320
321 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
322 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
323
324 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
325 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
326
327 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
328
329
330 menu_cfg_soap_editing = wx.Menu()
331
332 ID = wx.NewId()
333 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
334 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
335
336 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.'))
337 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item)
338
339 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
340
341 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
342
343
344 menu_cfg_ext_tools = wx.Menu()
345
346
347
348
349
350 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
351 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
352
353 ID = wx.NewId()
354 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
355 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
356
357 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
358 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
359
360 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
361 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
362
363 item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.'))
364 self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item)
365
366 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
367 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
368
369 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
370 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
371
372 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
373 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
374
375 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
376 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
377
378 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
379
380
381 menu_cfg_emr = wx.Menu()
382
383 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
384 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
385
386 item = menu_cfg_emr.Append(-1, _('Primary doctor'), _('Select the primary doctor to fall back to for patients without one.'))
387 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
388
389
390 menu_cfg_encounter = wx.Menu()
391
392 ID = wx.NewId()
393 menu_cfg_encounter.Append(ID, _('Edit on patient change'), _('Edit encounter details on changing of patients.'))
394 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
395
396 ID = wx.NewId()
397 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
398 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
399
400 ID = wx.NewId()
401 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
402 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
403
404 ID = wx.NewId()
405 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
406 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
407
408 ID = wx.NewId()
409 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
410 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
411
412 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
413
414
415 menu_cfg_episode = wx.Menu()
416
417 ID = wx.NewId()
418 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
419 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
420
421 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
422 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
423 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
424
425
426 menu_master_data = wx.Menu()
427
428 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
429 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
430
431 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
432 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
433
434 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
435 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
436
437 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
438 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
439
440 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
441
442
443 menu_users = wx.Menu()
444
445 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
446 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
447
448 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
449 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
450
451 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
452 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
453
454 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
455
456
457 menu_gnumed.AppendSeparator()
458
459 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
460 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
461
462 self.mainmenu.Append(menu_gnumed, '&GNUmed')
463
464
465 menu_person = wx.Menu()
466
467 ID_CREATE_PATIENT = wx.NewId()
468 menu_person.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
469 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
470
471 ID_LOAD_EXT_PAT = wx.NewId()
472 menu_person.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
473 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
474
475 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
476 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
477
478 ID_DEL_PAT = wx.NewId()
479 menu_person.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
480 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
481
482 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
483 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
484
485 menu_person.AppendSeparator()
486
487 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
488 menu_person.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
489 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
490
491
492 ID = wx.NewId()
493 menu_person.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
494 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
495
496 menu_person.AppendSeparator()
497
498 self.mainmenu.Append(menu_person, '&Person')
499 self.__gb['main.patientmenu'] = menu_person
500
501
502 menu_emr = wx.Menu()
503
504
505 menu_emr_show = wx.Menu()
506
507 item = menu_emr_show.Append(-1, _('Summary'), _('Show a high-level summary of the EMR.'))
508 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
509
510 menu_emr.AppendMenu(wx.NewId(), _('Show as ...'), menu_emr_show)
511 self.__gb['main.emr_showmenu'] = menu_emr_show
512
513
514 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
515 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
516
517 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
518 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
519
520
521 menu_emr_edit = wx.Menu()
522
523 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'))
524 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
525
526 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
527 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
528
529 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
530 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
531
532 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
533 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
534
535 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
536 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
537
538 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
539 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
540
541 item = menu_emr_edit.Append(-1, _('&Measurement(s)'), _('Add (a) measurement result(s) for the current patient.'))
542 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
543
544 item = menu_emr_edit.Append(-1, _('&Vaccination(s)'), _('Add (a) vaccination(s) for the current patient.'))
545 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
546
547 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
548
549
550
551 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
552 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
553
554 item = menu_emr.Append(-1, _('&Encounters list'), _('List all encounters including empty ones.'))
555 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
556
557 menu_emr.AppendSeparator()
558
559
560 menu_emr_export = wx.Menu()
561
562 ID_EXPORT_EMR_ASCII = wx.NewId()
563 menu_emr_export.Append (
564 ID_EXPORT_EMR_ASCII,
565 _('Text document'),
566 _("Export the EMR of the active patient into a text file")
567 )
568 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
569
570 ID_EXPORT_EMR_JOURNAL = wx.NewId()
571 menu_emr_export.Append (
572 ID_EXPORT_EMR_JOURNAL,
573 _('Journal'),
574 _("Export the EMR of the active patient as a chronological journal into a text file")
575 )
576 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
577
578 ID_EXPORT_MEDISTAR = wx.NewId()
579 menu_emr_export.Append (
580 ID_EXPORT_MEDISTAR,
581 _('MEDISTAR import format'),
582 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
583 )
584 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
585
586 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
587
588 menu_emr.AppendSeparator()
589
590 self.mainmenu.Append(menu_emr, _("&EMR"))
591 self.__gb['main.emrmenu'] = menu_emr
592
593
594 menu_paperwork = wx.Menu()
595
596 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
597 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
598
599 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
600
601
602 self.menu_tools = wx.Menu()
603
604 ID_DICOM_VIEWER = wx.NewId()
605 viewer = _('no viewer installed')
606 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
607 viewer = u'OsiriX'
608 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
609 viewer = u'Aeskulap'
610 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
611 viewer = u'AMIDE'
612 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
613 viewer = u'DicomScope'
614 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
615 viewer = u'(x)medcon'
616 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)
617 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
618 if viewer == _('no viewer installed'):
619 _log.info('neither of OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
620 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
621
622
623
624
625
626 ID = wx.NewId()
627 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
628 wx.EVT_MENU(self, ID, self.__on_snellen)
629
630 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
631 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
632
633 item = self.menu_tools.Append(-1, _('arriba'), _('arriba: cardiovascular risk assessment (%s).') % u'www.arriba-hausarzt.de')
634 self.Bind(wx.EVT_MENU, self.__on_arriba, item)
635
636 self.menu_tools.AppendSeparator()
637
638 self.mainmenu.Append(self.menu_tools, _("&Tools"))
639 self.__gb['main.toolsmenu'] = self.menu_tools
640
641
642 menu_knowledge = wx.Menu()
643
644
645 menu_drug_dbs = wx.Menu()
646
647 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
648 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
649
650
651
652
653
654
655 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
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 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
669 self.__gb['main.knowledgemenu'] = menu_knowledge
670
671
672 self.menu_office = wx.Menu()
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 self.mainmenu.Append(self.menu_office, _('&Office'))
680 self.__gb['main.officemenu'] = self.menu_office
681
682
683 help_menu = wx.Menu()
684
685 ID = wx.NewId()
686 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
687 wx.EVT_MENU(self, ID, self.__on_display_wiki)
688
689 ID = wx.NewId()
690 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
691 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
692
693 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
694 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
695
696 menu_debugging = wx.Menu()
697
698 ID_SCREENSHOT = wx.NewId()
699 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
700 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
701
702 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
703 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
704
705 ID = wx.NewId()
706 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
707 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
708
709 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
710 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
711
712 ID = wx.NewId()
713 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
714 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
715
716 ID_UNBLOCK = wx.NewId()
717 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
718 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
719
720 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
721 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
722
723
724
725
726 if _cfg.get(option = 'debug'):
727 ID_TOGGLE_PAT_LOCK = wx.NewId()
728 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient'), _('Lock/unlock patient - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
729 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
730
731 ID_TEST_EXCEPTION = wx.NewId()
732 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
733 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
734
735 ID = wx.NewId()
736 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
737 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
738 try:
739 import wx.lib.inspection
740 except ImportError:
741 menu_debugging.Enable(id = ID, enable = False)
742
743 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
744
745 help_menu.AppendSeparator()
746
747 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
748 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
749
750 ID_CONTRIBUTORS = wx.NewId()
751 help_menu.Append(ID_CONTRIBUTORS, _('GNUmed contributors'), _('show GNUmed contributors'))
752 wx.EVT_MENU(self, ID_CONTRIBUTORS, self.__on_show_contributors)
753
754 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
755 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
756
757 help_menu.AppendSeparator()
758
759 self.mainmenu.Append(help_menu, _("&Help"))
760
761 self.__gb['main.helpmenu'] = help_menu
762
763
764 self.SetMenuBar(self.mainmenu)
765
768
769
770
772 """register events we want to react to"""
773
774 wx.EVT_CLOSE(self, self.OnClose)
775 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
776 wx.EVT_END_SESSION(self, self._on_end_session)
777
778 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
779 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
780 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
781 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
782 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
783 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
784 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
785 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
786
787 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
788
789 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
790
791 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
792
793 _log.debug('registering plugin with menu system')
794 _log.debug(' generic name: %s', plugin_name)
795 _log.debug(' class name: %s', class_name)
796 _log.debug(' specific menu: %s', menu_name)
797 _log.debug(' menu item: %s', menu_item_name)
798
799
800 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
801 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
802 self.menu_id2plugin[item.Id] = class_name
803
804
805 if menu_name is not None:
806 menu = self.__gb['main.%smenu' % menu_name]
807 item = menu.Append(-1, menu_item_name, menu_help_string)
808 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
809 self.menu_id2plugin[item.Id] = class_name
810
811 return True
812
814 gmDispatcher.send (
815 signal = u'display_widget',
816 name = self.menu_id2plugin[evt.Id]
817 )
818
820 wx.Bell()
821 wx.Bell()
822 wx.Bell()
823 _log.warning('unhandled event detected: QUERY_END_SESSION')
824 _log.info('we should be saving ourselves from here')
825 gmLog2.flush()
826 print "unhandled event detected: QUERY_END_SESSION"
827
829 wx.Bell()
830 wx.Bell()
831 wx.Bell()
832 _log.warning('unhandled event detected: END_SESSION')
833 gmLog2.flush()
834 print "unhandled event detected: END_SESSION"
835
837 if not callable(callback):
838 raise TypeError(u'callback [%s] not callable' % callback)
839
840 self.__pre_exit_callbacks.append(callback)
841
842 - def _on_set_statustext_pubsub(self, context=None):
843 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
844 wx.CallAfter(self.SetStatusText, msg)
845
846 try:
847 if context.data['beep']:
848 wx.Bell()
849 except KeyError:
850 pass
851
852 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
853
854 if msg is None:
855 msg = _('programmer forgot to specify status message')
856
857 if loglevel is not None:
858 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
859
860 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
861 wx.CallAfter(self.SetStatusText, msg)
862
863 if beep:
864 wx.Bell()
865
867 wx.CallAfter(self.__on_db_maintenance_warning)
868
870
871 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
872 wx.Bell()
873 if not wx.GetApp().IsActive():
874 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
875
876 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
877
878 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
879 None,
880 -1,
881 caption = _('Database shutdown warning'),
882 question = _(
883 'The database will be shut down for maintenance\n'
884 'in a few minutes.\n'
885 '\n'
886 'In order to not suffer any loss of data you\n'
887 'will need to save your current work and log\n'
888 'out of this GNUmed client.\n'
889 ),
890 button_defs = [
891 {
892 u'label': _('Close now'),
893 u'tooltip': _('Close this GNUmed client immediately.'),
894 u'default': False
895 },
896 {
897 u'label': _('Finish work'),
898 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
899 u'default': True
900 }
901 ]
902 )
903 decision = dlg.ShowModal()
904 if decision == wx.ID_YES:
905 top_win = wx.GetApp().GetTopWindow()
906 wx.CallAfter(top_win.Close)
907
909 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
910
912
913 if not wx.GetApp().IsActive():
914 if urgent:
915 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
916 else:
917 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
918
919 if msg is not None:
920 self.SetStatusText(msg)
921
922 if urgent:
923 wx.Bell()
924
925 gmHooks.run_hook_script(hook = u'request_user_attention')
926
928 wx.CallAfter(self.__on_pat_name_changed)
929
931 self.__update_window_title()
932
934 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
935
937 self.__update_window_title()
938 try:
939 gmHooks.run_hook_script(hook = u'post_patient_activation')
940 except:
941 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
942 raise
943
945 return self.__sanity_check_encounter()
946
1003
1004
1005
1008
1016
1017
1018
1033
1056
1058 from Gnumed.wxpython import gmAbout
1059 contribs = gmAbout.cContributorsDlg (
1060 parent = self,
1061 id = -1,
1062 title = _('GNUmed contributors'),
1063 size = wx.Size(400,600),
1064 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1065 )
1066 contribs.ShowModal()
1067 del contribs
1068 del gmAbout
1069
1070
1071
1073 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1074 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1075 self.Close(True)
1076 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1077
1080
1082 send = gmGuiHelpers.gm_show_question (
1083 _('This will send a notification about database downtime\n'
1084 'to all GNUmed clients connected to your database.\n'
1085 '\n'
1086 'Do you want to send the notification ?\n'
1087 ),
1088 _('Announcing database maintenance downtime')
1089 )
1090 if not send:
1091 return
1092 gmPG2.send_maintenance_notification()
1093
1094
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1144
1145 gmCfgWidgets.configure_string_option (
1146 message = _(
1147 'Some network installations cannot cope with loading\n'
1148 'documents of arbitrary size in one piece from the\n'
1149 'database (mainly observed on older Windows versions)\n.'
1150 '\n'
1151 'Under such circumstances documents need to be retrieved\n'
1152 'in chunks and reassembled on the client.\n'
1153 '\n'
1154 'Here you can set the size (in Bytes) above which\n'
1155 'GNUmed will retrieve documents in chunks. Setting this\n'
1156 'value to 0 will disable the chunking protocol.'
1157 ),
1158 option = 'horstspace.blob_export_chunk_size',
1159 bias = 'workplace',
1160 default_value = 1024 * 1024,
1161 validator = is_valid
1162 )
1163
1164
1165
1233
1237
1238
1239
1248
1249 gmCfgWidgets.configure_string_option (
1250 message = _(
1251 'When GNUmed cannot find an OpenOffice server it\n'
1252 'will try to start one. OpenOffice, however, needs\n'
1253 'some time to fully start up.\n'
1254 '\n'
1255 'Here you can set the time for GNUmed to wait for OOo.\n'
1256 ),
1257 option = 'external.ooo.startup_settle_time',
1258 bias = 'workplace',
1259 default_value = 2.0,
1260 validator = is_valid
1261 )
1262
1265
1280
1281 gmCfgWidgets.configure_string_option (
1282 message = _(
1283 'GNUmed will use this URL to access a website which lets\n'
1284 'you report an adverse drug reaction (ADR).\n'
1285 '\n'
1286 'If you leave this empty it will fall back\n'
1287 'to an URL for reporting ADRs in Germany.'
1288 ),
1289 option = 'external.urls.report_ADR',
1290 bias = 'user',
1291 default_value = german_default,
1292 validator = is_valid
1293 )
1294
1308
1309 gmCfgWidgets.configure_string_option (
1310 message = _(
1311 'GNUmed will use this URL to access a website which lets\n'
1312 'you report an adverse vaccination reaction (vADR).\n'
1313 '\n'
1314 'If you set it to a specific address that URL must be\n'
1315 'accessible now. If you leave it empty it will fall back\n'
1316 'to the URL for reporting other adverse drug reactions.'
1317 ),
1318 option = 'external.urls.report_vaccine_ADR',
1319 bias = 'user',
1320 default_value = german_default,
1321 validator = is_valid
1322 )
1323
1337
1338 gmCfgWidgets.configure_string_option (
1339 message = _(
1340 'GNUmed will use this URL to access an encyclopedia of\n'
1341 'measurement/lab methods from within the measurments grid.\n'
1342 '\n'
1343 'You can leave this empty but to set it to a specific\n'
1344 'address the URL must be accessible now.'
1345 ),
1346 option = 'external.urls.measurements_encyclopedia',
1347 bias = 'user',
1348 default_value = german_default,
1349 validator = is_valid
1350 )
1351
1365
1366 gmCfgWidgets.configure_string_option (
1367 message = _(
1368 'GNUmed will use this URL to access a page showing\n'
1369 'vaccination schedules.\n'
1370 '\n'
1371 'You can leave this empty but to set it to a specific\n'
1372 'address the URL must be accessible now.'
1373 ),
1374 option = 'external.urls.vaccination_plans',
1375 bias = 'user',
1376 default_value = german_default,
1377 validator = is_valid
1378 )
1379
1392
1393 gmCfgWidgets.configure_string_option (
1394 message = _(
1395 'Enter the shell command with which to start the\n'
1396 'the ACS risk assessment calculator.\n'
1397 '\n'
1398 'GNUmed will try to verify the path which may,\n'
1399 'however, fail if you are using an emulator such\n'
1400 'as Wine. Nevertheless, starting the calculator\n'
1401 'will work as long as the shell command is correct\n'
1402 'despite the failing test.'
1403 ),
1404 option = 'external.tools.acs_risk_calculator_cmd',
1405 bias = 'user',
1406 validator = is_valid
1407 )
1408
1411
1424
1425 gmCfgWidgets.configure_string_option (
1426 message = _(
1427 'Enter the shell command with which to start\n'
1428 'the FreeDiams drug database frontend.\n'
1429 '\n'
1430 'GNUmed will try to verify that path.'
1431 ),
1432 option = 'external.tools.freediams_cmd',
1433 bias = 'workplace',
1434 default_value = None,
1435 validator = is_valid
1436 )
1437
1450
1451 gmCfgWidgets.configure_string_option (
1452 message = _(
1453 'Enter the shell command with which to start the\n'
1454 'the IFAP drug database.\n'
1455 '\n'
1456 'GNUmed will try to verify the path which may,\n'
1457 'however, fail if you are using an emulator such\n'
1458 'as Wine. Nevertheless, starting IFAP will work\n'
1459 'as long as the shell command is correct despite\n'
1460 'the failing test.'
1461 ),
1462 option = 'external.ifap-win.shell_command',
1463 bias = 'workplace',
1464 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1465 validator = is_valid
1466 )
1467
1468
1469
1518
1519
1520
1537
1540
1543
1548
1549 gmCfgWidgets.configure_string_option (
1550 message = _(
1551 'When a patient is activated GNUmed checks the\n'
1552 "proximity of the patient's birthday.\n"
1553 '\n'
1554 'If the birthday falls within the range of\n'
1555 ' "today %s <the interval you set here>"\n'
1556 'GNUmed will remind you of the recent or\n'
1557 'imminent anniversary.'
1558 ) % u'\u2213',
1559 option = u'patient_search.dob_warn_interval',
1560 bias = 'user',
1561 default_value = '1 week',
1562 validator = is_valid
1563 )
1564
1566
1567 gmCfgWidgets.configure_boolean_option (
1568 parent = self,
1569 question = _(
1570 'When adding progress notes do you want to\n'
1571 'allow opening several unassociated, new\n'
1572 'episodes for a patient at once ?\n'
1573 '\n'
1574 'This can be particularly helpful when entering\n'
1575 'progress notes on entirely new patients presenting\n'
1576 'with a multitude of problems on their first visit.'
1577 ),
1578 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1579 button_tooltips = [
1580 _('Yes, allow for multiple new episodes concurrently.'),
1581 _('No, only allow editing one new episode at a time.')
1582 ]
1583 )
1584
1586
1587 gmCfgWidgets.configure_boolean_option (
1588 parent = self,
1589 question = _(
1590 'When activating a patient, do you want GNUmed to\n'
1591 'auto-open editors for all active problems that were\n'
1592 'touched upon during the current and the most recent\n'
1593 'encounter ?'
1594 ),
1595 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1596 button_tooltips = [
1597 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1598 _('No, only auto-open one editor for a new, unassociated problem.')
1599 ]
1600 )
1601
1647
1648
1649
1652
1655
1669
1671 gmCfgWidgets.configure_boolean_option (
1672 parent = self,
1673 question = _(
1674 'Do you want GNUmed to show the encounter\n'
1675 'details editor when changing the active patient ?'
1676 ),
1677 option = 'encounter.show_editor_before_patient_change',
1678 button_tooltips = [
1679 _('Yes, show the encounter editor if it seems appropriate.'),
1680 _('No, never show the encounter editor even if it would seem useful.')
1681 ]
1682 )
1683
1688
1689 gmCfgWidgets.configure_string_option (
1690 message = _(
1691 'When a patient is activated GNUmed checks the\n'
1692 'chart for encounters lacking any entries.\n'
1693 '\n'
1694 'Any such encounters older than what you set\n'
1695 'here will be removed from the medical record.\n'
1696 '\n'
1697 'To effectively disable removal of such encounters\n'
1698 'set this option to an improbable value.\n'
1699 ),
1700 option = 'encounter.ttl_if_empty',
1701 bias = 'user',
1702 default_value = '1 week',
1703 validator = is_valid
1704 )
1705
1710
1711 gmCfgWidgets.configure_string_option (
1712 message = _(
1713 'When a patient is activated GNUmed checks the\n'
1714 'age of the most recent encounter.\n'
1715 '\n'
1716 'If that encounter is younger than this age\n'
1717 'the existing encounter will be continued.\n'
1718 '\n'
1719 '(If it is really old a new encounter is\n'
1720 ' started, or else GNUmed will ask you.)\n'
1721 ),
1722 option = 'encounter.minimum_ttl',
1723 bias = 'user',
1724 default_value = '1 hour 30 minutes',
1725 validator = is_valid
1726 )
1727
1732
1733 gmCfgWidgets.configure_string_option (
1734 message = _(
1735 'When a patient is activated GNUmed checks the\n'
1736 'age of the most recent encounter.\n'
1737 '\n'
1738 'If that encounter is older than this age\n'
1739 'GNUmed will always start a new encounter.\n'
1740 '\n'
1741 '(If it is very recent the existing encounter\n'
1742 ' is continued, or else GNUmed will ask you.)\n'
1743 ),
1744 option = 'encounter.maximum_ttl',
1745 bias = 'user',
1746 default_value = '6 hours',
1747 validator = is_valid
1748 )
1749
1758
1759 gmCfgWidgets.configure_string_option (
1760 message = _(
1761 'At any time there can only be one open (ongoing)\n'
1762 'episode for each health issue.\n'
1763 '\n'
1764 'When you try to open (add data to) an episode on a health\n'
1765 'issue GNUmed will check for an existing open episode on\n'
1766 'that issue. If there is any it will check the age of that\n'
1767 'episode. The episode is closed if it has been dormant (no\n'
1768 'data added, that is) for the period of time (in days) you\n'
1769 'set here.\n'
1770 '\n'
1771 "If the existing episode hasn't been dormant long enough\n"
1772 'GNUmed will consult you what to do.\n'
1773 '\n'
1774 'Enter maximum episode dormancy in DAYS:'
1775 ),
1776 option = 'episode.ttl',
1777 bias = 'user',
1778 default_value = 60,
1779 validator = is_valid
1780 )
1781
1812
1827
1852
1864
1865 gmCfgWidgets.configure_string_option (
1866 message = _(
1867 'GNUmed can check for new releases being available. To do\n'
1868 'so it needs to load version information from an URL.\n'
1869 '\n'
1870 'The default URL is:\n'
1871 '\n'
1872 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1873 '\n'
1874 'but you can configure any other URL locally. Note\n'
1875 'that you must enter the location as a valid URL.\n'
1876 'Depending on the URL the client will need online\n'
1877 'access when checking for updates.'
1878 ),
1879 option = u'horstspace.update.url',
1880 bias = u'workplace',
1881 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1882 validator = is_valid
1883 )
1884
1902
1919
1930
1931 gmCfgWidgets.configure_string_option (
1932 message = _(
1933 'GNUmed can show the document review dialog after\n'
1934 'calling the appropriate viewer for that document.\n'
1935 '\n'
1936 'Select the conditions under which you want\n'
1937 'GNUmed to do so:\n'
1938 '\n'
1939 ' 0: never display the review dialog\n'
1940 ' 1: always display the dialog\n'
1941 ' 2: only if there is no previous review by me\n'
1942 '\n'
1943 'Note that if a viewer is configured to not block\n'
1944 'GNUmed during document display the review dialog\n'
1945 'will actually appear in parallel to the viewer.'
1946 ),
1947 option = u'horstspace.document_viewer.review_after_display',
1948 bias = u'user',
1949 default_value = 2,
1950 validator = is_valid
1951 )
1952
1954
1955
1956 master_data_lists = [
1957 'adr',
1958 'drugs',
1959 'codes',
1960 'substances_in_brands',
1961 'substances',
1962 'labs',
1963 'form_templates',
1964 'doc_types',
1965 'enc_types',
1966 'text_expansions',
1967 'meta_test_types',
1968
1969 'patient_tags',
1970 'provinces',
1971 'db_translations',
1972 'test_types',
1973 'org_units',
1974 'vacc_indications',
1975 'vaccines',
1976 'workplaces'
1977 ]
1978
1979 master_data_list_names = {
1980 'adr': _('Addresses (likely slow)'),
1981 'drugs': _('Branded drugs (as marketed)'),
1982 'codes': _('Codes and their respective terms'),
1983 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
1984 'labs': _('Diagnostic organizations (path labs, ...)'),
1985 'form_templates': _('Document templates (forms, letters, plots, ...)'),
1986 'doc_types': _('Document types'),
1987 'enc_types': _('Encounter types'),
1988 'text_expansions': _('Keyword based text expansion macros'),
1989 'meta_test_types': _('Meta test/measurement types'),
1990
1991 'patient_tags': _('Patient tags'),
1992 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
1993 'db_translations': _('String translations in the database'),
1994 'test_types': _('Test/measurement types'),
1995 'org_units': _('Units of organizations (branches, sites, departments, parts, ...'),
1996 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
1997 'vaccines': _('Vaccines'),
1998 'workplaces': _('Workplace profiles (which plugins to load)'),
1999 'substances': _('Consumable substances')
2000 }
2001
2002 map_list2handler = {
2003 'org_units': gmOrganizationWidgets.manage_org_units,
2004 'form_templates': gmFormWidgets.manage_form_templates,
2005 'doc_types': gmDocumentWidgets.manage_document_types,
2006 'text_expansions': gmProviderInboxWidgets.configure_keyword_text_expansion,
2007 'db_translations': gmI18nWidgets.manage_translations,
2008 'codes': gmCodingWidgets.browse_coded_terms,
2009 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2010 'provinces': gmPersonContactWidgets.manage_provinces,
2011 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins,
2012 'drugs': gmMedicationWidgets.manage_branded_drugs,
2013 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2014 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2015 'test_types': gmMeasurementWidgets.manage_measurement_types,
2016 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2017 'vaccines': gmVaccWidgets.manage_vaccines,
2018 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2019
2020 'adr': gmPersonContactWidgets.manage_addresses,
2021 'substances': gmMedicationWidgets.manage_consumable_substances,
2022 'patient_tags': gmDemographicsWidgets.manage_tag_images
2023 }
2024
2025
2026 def edit(item):
2027 try: map_list2handler[item](parent = self)
2028 except KeyError: pass
2029 return False
2030
2031
2032 gmListWidgets.get_choices_from_list (
2033 parent = self,
2034 caption = _('Master data management'),
2035 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2036 data = master_data_lists,
2037 columns = [_('Select the list you want to manage:')],
2038 edit_callback = edit,
2039 single_selection = True,
2040 ignore_OK_button = True
2041 )
2042
2056
2058 curr_pat = gmPerson.gmCurrentPatient()
2059
2060 arriba = gmArriba.cArriba()
2061 if not arriba.run(patient = curr_pat, debug = _cfg.get(option = 'debug')):
2062 return
2063
2064
2065 if curr_pat is None:
2066 return
2067
2068 if arriba.pdf_result is None:
2069 return
2070
2071 doc = gmDocumentWidgets.save_file_as_new_document (
2072 parent = self,
2073 filename = arriba.pdf_result,
2074 document_type = _('risk assessment')
2075 )
2076
2077 try: os.remove(arriba.pdf_result)
2078 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2079
2080 if doc is None:
2081 return
2082
2083 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2084 doc.save()
2085
2086 try:
2087 open(arriba.xml_result).close()
2088 part = doc.add_part(file = arriba.xml_result)
2089 except StandardError:
2090 _log.exception('error accessing [%s]', arriba.xml_result)
2091 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2092
2093 if part is None:
2094 return
2095
2096 part['obj_comment'] = u'XML-Daten'
2097 part['filename'] = u'arriba-result.xml'
2098 part.save()
2099
2101
2102 dbcfg = gmCfg.cCfgSQL()
2103 cmd = dbcfg.get2 (
2104 option = u'external.tools.acs_risk_calculator_cmd',
2105 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2106 bias = 'user'
2107 )
2108
2109 if cmd is None:
2110 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2111 return
2112
2113 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
2114 try:
2115 subprocess.check_call (
2116 args = (cmd,),
2117 close_fds = True,
2118 cwd = cwd
2119 )
2120 except (OSError, ValueError, subprocess.CalledProcessError):
2121 _log.exception('there was a problem executing [%s]', cmd)
2122 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2123 return
2124
2125 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2126 for pdf in pdfs:
2127 try:
2128 open(pdf).close()
2129 except:
2130 _log.exception('error accessing [%s]', pdf)
2131 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2132 continue
2133
2134 doc = gmDocumentWidgets.save_file_as_new_document (
2135 parent = self,
2136 filename = pdf,
2137 document_type = u'risk assessment'
2138 )
2139
2140 try:
2141 os.remove(pdf)
2142 except StandardError:
2143 _log.exception('cannot remove [%s]', pdf)
2144
2145 if doc is None:
2146 continue
2147 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2148 doc.save()
2149
2150 return
2151
2153 dlg = gmSnellen.cSnellenCfgDlg()
2154 if dlg.ShowModal() != wx.ID_OK:
2155 return
2156
2157 frame = gmSnellen.cSnellenChart (
2158 width = dlg.vals[0],
2159 height = dlg.vals[1],
2160 alpha = dlg.vals[2],
2161 mirr = dlg.vals[3],
2162 parent = None
2163 )
2164 frame.CentreOnScreen(wx.BOTH)
2165
2166
2167 frame.Show(True)
2168
2169
2171 webbrowser.open (
2172 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
2173 new = False,
2174 autoraise = True
2175 )
2176
2179
2181 webbrowser.open (
2182 url = 'http://www.kompendium.ch',
2183 new = False,
2184 autoraise = True
2185 )
2186
2187
2188
2192
2193
2194
2196 wx.CallAfter(self.__save_screenshot)
2197 evt.Skip()
2198
2200
2201 time.sleep(0.5)
2202
2203 rect = self.GetRect()
2204
2205
2206 if sys.platform == 'linux2':
2207 client_x, client_y = self.ClientToScreen((0, 0))
2208 border_width = client_x - rect.x
2209 title_bar_height = client_y - rect.y
2210
2211 if self.GetMenuBar():
2212 title_bar_height /= 2
2213 rect.width += (border_width * 2)
2214 rect.height += title_bar_height + border_width
2215
2216 wdc = wx.ScreenDC()
2217 mdc = wx.MemoryDC()
2218 img = wx.EmptyBitmap(rect.width, rect.height)
2219 mdc.SelectObject(img)
2220 mdc.Blit (
2221 0, 0,
2222 rect.width, rect.height,
2223 wdc,
2224 rect.x, rect.y
2225 )
2226
2227
2228 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2229 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2230 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2231
2233
2234 raise ValueError('raised ValueError to test exception handling')
2235
2237 import wx.lib.inspection
2238 wx.lib.inspection.InspectionTool().Show()
2239
2241 webbrowser.open (
2242 url = 'https://bugs.launchpad.net/gnumed/',
2243 new = False,
2244 autoraise = True
2245 )
2246
2248 webbrowser.open (
2249 url = 'http://wiki.gnumed.de',
2250 new = False,
2251 autoraise = True
2252 )
2253
2255 webbrowser.open (
2256 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
2257 new = False,
2258 autoraise = True
2259 )
2260
2262 webbrowser.open (
2263 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
2264 new = False,
2265 autoraise = True
2266 )
2267
2274
2278
2281
2288
2293
2295 name = os.path.basename(gmLog2._logfile_name)
2296 name, ext = os.path.splitext(name)
2297 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2298 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2299
2300 dlg = wx.FileDialog (
2301 parent = self,
2302 message = _("Save current log as..."),
2303 defaultDir = new_path,
2304 defaultFile = new_name,
2305 wildcard = "%s (*.log)|*.log" % _("log files"),
2306 style = wx.SAVE
2307 )
2308 choice = dlg.ShowModal()
2309 new_name = dlg.GetPath()
2310 dlg.Destroy()
2311 if choice != wx.ID_OK:
2312 return True
2313
2314 _log.warning('syncing log file for backup to [%s]', new_name)
2315 gmLog2.flush()
2316 shutil.copy2(gmLog2._logfile_name, new_name)
2317 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2318
2321
2322
2323
2325 """This is the wx.EVT_CLOSE handler.
2326
2327 - framework still functional
2328 """
2329 _log.debug('gmTopLevelFrame.OnClose() start')
2330 self._clean_exit()
2331 self.Destroy()
2332 _log.debug('gmTopLevelFrame.OnClose() end')
2333 return True
2334
2340
2345
2353
2360
2367
2377
2385
2393
2401
2409
2418
2426
2443
2446
2449
2451
2452 pat = gmPerson.gmCurrentPatient()
2453 if not pat.connected:
2454 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2455 return False
2456
2457 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2458
2459 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2460 gmTools.mkdir(aDefDir)
2461
2462 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2463 dlg = wx.FileDialog (
2464 parent = self,
2465 message = _("Save patient's EMR journal as..."),
2466 defaultDir = aDefDir,
2467 defaultFile = fname,
2468 wildcard = aWildcard,
2469 style = wx.SAVE
2470 )
2471 choice = dlg.ShowModal()
2472 fname = dlg.GetPath()
2473 dlg.Destroy()
2474 if choice != wx.ID_OK:
2475 return True
2476
2477 _log.debug('exporting EMR journal to [%s]' % fname)
2478
2479 exporter = gmPatientExporter.cEMRJournalExporter()
2480
2481 wx.BeginBusyCursor()
2482 try:
2483 fname = exporter.export_to_file(filename = fname)
2484 except:
2485 wx.EndBusyCursor()
2486 gmGuiHelpers.gm_show_error (
2487 _('Error exporting patient EMR as chronological journal.'),
2488 _('EMR journal export')
2489 )
2490 raise
2491 wx.EndBusyCursor()
2492
2493 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2494
2495 return True
2496
2503
2505 curr_pat = gmPerson.gmCurrentPatient()
2506 if not curr_pat.connected:
2507 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2508 return
2509
2510 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2511 if tag is None:
2512 return
2513
2514 tag = curr_pat.add_tag(tag['pk_tag_image'])
2515 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2516 comment = wx.GetTextFromUser (
2517 message = msg,
2518 caption = _('Editing tag comment'),
2519 default_value = gmTools.coalesce(tag['comment'], u''),
2520 parent = self
2521 )
2522
2523 if comment == u'':
2524 return
2525
2526 if comment.strip() == tag['comment']:
2527 return
2528
2529 if comment == u' ':
2530 tag['comment'] = None
2531 else:
2532 tag['comment'] = comment.strip()
2533
2534 tag.save()
2535
2545
2547 curr_pat = gmPerson.gmCurrentPatient()
2548 if not curr_pat.connected:
2549 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2550 return False
2551
2552 enc = 'cp850'
2553 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2554 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2555 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2556
2559
2567
2575
2578
2585
2589
2592
2595
2598
2603
2605 """Cleanup helper.
2606
2607 - should ALWAYS be called when this program is
2608 to be terminated
2609 - ANY code that should be executed before a
2610 regular shutdown should go in here
2611 - framework still functional
2612 """
2613 _log.debug('gmTopLevelFrame._clean_exit() start')
2614
2615
2616 listener = gmBackendListener.gmBackendListener()
2617 try:
2618 listener.shutdown()
2619 except:
2620 _log.exception('cannot stop backend notifications listener thread')
2621
2622
2623 if _scripting_listener is not None:
2624 try:
2625 _scripting_listener.shutdown()
2626 except:
2627 _log.exception('cannot stop scripting listener thread')
2628
2629
2630 self.clock_update_timer.Stop()
2631 gmTimer.shutdown()
2632 gmPhraseWheel.shutdown()
2633
2634
2635 for call_back in self.__pre_exit_callbacks:
2636 try:
2637 call_back()
2638 except:
2639 print "*** pre-exit callback failed ***"
2640 print call_back
2641 _log.exception('callback [%s] failed', call_back)
2642
2643
2644 gmDispatcher.send(u'application_closing')
2645
2646
2647 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2648
2649
2650 curr_width, curr_height = self.GetClientSizeTuple()
2651 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2652 dbcfg = gmCfg.cCfgSQL()
2653 dbcfg.set (
2654 option = 'main.window.width',
2655 value = curr_width,
2656 workplace = gmSurgery.gmCurrentPractice().active_workplace
2657 )
2658 dbcfg.set (
2659 option = 'main.window.height',
2660 value = curr_height,
2661 workplace = gmSurgery.gmCurrentPractice().active_workplace
2662 )
2663
2664 if _cfg.get(option = 'debug'):
2665 print '---=== GNUmed shutdown ===---'
2666 try:
2667 print _('You have to manually close this window to finalize shutting down GNUmed.')
2668 print _('This is so that you can inspect the console output at your leisure.')
2669 except UnicodeEncodeError:
2670 print 'You have to manually close this window to finalize shutting down GNUmed.'
2671 print 'This is so that you can inspect the console output at your leisure.'
2672 print '---=== GNUmed shutdown ===---'
2673
2674
2675 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2676
2677
2678 import threading
2679 _log.debug("%s active threads", threading.activeCount())
2680 for t in threading.enumerate():
2681 _log.debug('thread %s', t)
2682
2683 _log.debug('gmTopLevelFrame._clean_exit() end')
2684
2685
2686
2688
2689 if _cfg.get(option = 'slave'):
2690 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2691 _cfg.get(option = 'slave personality'),
2692 _cfg.get(option = 'xml-rpc port')
2693 )
2694 else:
2695 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2696
2698 """Update title of main window based on template.
2699
2700 This gives nice tooltips on iconified GNUmed instances.
2701
2702 User research indicates that in the title bar people want
2703 the date of birth, not the age, so please stick to this
2704 convention.
2705 """
2706 args = {}
2707
2708 pat = gmPerson.gmCurrentPatient()
2709 if pat.connected:
2710 args['pat'] = u'%s %s %s (%s) #%d' % (
2711 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2712 pat['firstnames'],
2713 pat['lastnames'],
2714 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2715 pat['pk_identity']
2716 )
2717 else:
2718 args['pat'] = _('no patient')
2719
2720 args['prov'] = u'%s%s.%s' % (
2721 gmTools.coalesce(_provider['title'], u'', u'%s '),
2722 _provider['firstnames'][:1],
2723 _provider['lastnames']
2724 )
2725
2726 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2727
2728 self.SetTitle(self.__title_template % args)
2729
2730
2732 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2733 sb.SetStatusWidths([-1, 225])
2734
2735 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2736 self._cb_update_clock()
2737
2738 self.clock_update_timer.Start(milliseconds = 1000)
2739
2741 """Displays date and local time in the second slot of the status bar"""
2742 t = time.localtime(time.time())
2743 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2744 self.SetStatusText(st,1)
2745
2747 """Lock GNUmed client against unauthorized access"""
2748
2749
2750
2751 return
2752
2754 """Unlock the main notebook widgets
2755 As long as we are not logged into the database backend,
2756 all pages but the 'login' page of the main notebook widget
2757 are locked; i.e. not accessible by the user
2758 """
2759
2760
2761
2762
2763
2764 return
2765
2767 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2768
2770
2772
2773 self.__starting_up = True
2774
2775 gmExceptionHandlingWidgets.install_wx_exception_handler()
2776 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2777
2778
2779
2780
2781 self.SetAppName(u'gnumed')
2782 self.SetVendorName(u'The GNUmed Development Community.')
2783 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2784 paths.init_paths(wx = wx, app_name = u'gnumed')
2785
2786 if not self.__setup_prefs_file():
2787 return False
2788
2789 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2790
2791 self.__guibroker = gmGuiBroker.GuiBroker()
2792 self.__setup_platform()
2793
2794 if not self.__establish_backend_connection():
2795 return False
2796
2797 if not _cfg.get(option = 'skip-update-check'):
2798 self.__check_for_updates()
2799
2800 if _cfg.get(option = 'slave'):
2801 if not self.__setup_scripting_listener():
2802 return False
2803
2804
2805 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2806 frame.CentreOnScreen(wx.BOTH)
2807 self.SetTopWindow(frame)
2808 frame.Show(True)
2809
2810 if _cfg.get(option = 'debug'):
2811 self.RedirectStdio()
2812 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2813
2814
2815 print '---=== GNUmed startup ===---'
2816 print _('redirecting STDOUT/STDERR to this log window')
2817 print '---=== GNUmed startup ===---'
2818
2819 self.__setup_user_activity_timer()
2820 self.__register_events()
2821
2822 wx.CallAfter(self._do_after_init)
2823
2824 return True
2825
2827 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2828
2829 - after destroying all application windows and controls
2830 - before wx.Windows internal cleanup
2831 """
2832 _log.debug('gmApp.OnExit() start')
2833
2834 self.__shutdown_user_activity_timer()
2835
2836 if _cfg.get(option = 'debug'):
2837 self.RestoreStdio()
2838 sys.stdin = sys.__stdin__
2839 sys.stdout = sys.__stdout__
2840 sys.stderr = sys.__stderr__
2841
2842 _log.debug('gmApp.OnExit() end')
2843
2845 wx.Bell()
2846 wx.Bell()
2847 wx.Bell()
2848 _log.warning('unhandled event detected: QUERY_END_SESSION')
2849 _log.info('we should be saving ourselves from here')
2850 gmLog2.flush()
2851 print "unhandled event detected: QUERY_END_SESSION"
2852
2854 wx.Bell()
2855 wx.Bell()
2856 wx.Bell()
2857 _log.warning('unhandled event detected: END_SESSION')
2858 gmLog2.flush()
2859 print "unhandled event detected: END_SESSION"
2860
2871
2873 self.user_activity_detected = True
2874 evt.Skip()
2875
2877
2878 if self.user_activity_detected:
2879 self.elapsed_inactivity_slices = 0
2880 self.user_activity_detected = False
2881 self.elapsed_inactivity_slices += 1
2882 else:
2883 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2884
2885 pass
2886
2887 self.user_activity_timer.Start(oneShot = True)
2888
2889
2890
2892 try:
2893 kwargs['originated_in_database']
2894 print '==> got notification from database "%s":' % kwargs['signal']
2895 except KeyError:
2896 print '==> received signal from client: "%s"' % kwargs['signal']
2897
2898 del kwargs['signal']
2899 for key in kwargs.keys():
2900 print ' [%s]: %s' % (key, kwargs[key])
2901
2903 print "wx.lib.pubsub message:"
2904 print msg.topic
2905 print msg.data
2906
2912
2914 self.user_activity_detected = True
2915 self.elapsed_inactivity_slices = 0
2916
2917 self.max_user_inactivity_slices = 15
2918 self.user_activity_timer = gmTimer.cTimer (
2919 callback = self._on_user_activity_timer_expired,
2920 delay = 2000
2921 )
2922 self.user_activity_timer.Start(oneShot=True)
2923
2925 try:
2926 self.user_activity_timer.Stop()
2927 del self.user_activity_timer
2928 except:
2929 pass
2930
2932 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2933 wx.EVT_END_SESSION(self, self._on_end_session)
2934
2935
2936
2937
2938
2939 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2940
2941 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2942 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2943
2944 if _cfg.get(option = 'debug'):
2945 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
2946 _log.debug('connected old signal monitor')
2947 wx.lib.pubsub.Publisher().subscribe(listener = self._signal_debugging_monitor_pubsub)
2948 _log.debug('connected wx.lib.pubsub based signal monitor for all topics')
2949
2950
2951
2952
2953
2954
2970
2972 """Handle all the database related tasks necessary for startup."""
2973
2974
2975 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
2976
2977 from Gnumed.wxpython import gmAuthWidgets
2978 connected = gmAuthWidgets.connect_to_database (
2979 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
2980 require_version = not override
2981 )
2982 if not connected:
2983 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
2984 return False
2985
2986
2987 try:
2988 global _provider
2989 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
2990 except ValueError:
2991 account = gmPG2.get_current_user()
2992 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
2993 msg = _(
2994 'The database account [%s] cannot be used as a\n'
2995 'staff member login for GNUmed. There was an\n'
2996 'error retrieving staff details for it.\n\n'
2997 'Please ask your administrator for help.\n'
2998 ) % account
2999 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
3000 return False
3001
3002
3003 tmp = '%s%s %s (%s = %s)' % (
3004 gmTools.coalesce(_provider['title'], ''),
3005 _provider['firstnames'],
3006 _provider['lastnames'],
3007 _provider['short_alias'],
3008 _provider['db_user']
3009 )
3010 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
3011
3012
3013 surgery = gmSurgery.gmCurrentPractice()
3014 msg = surgery.db_logon_banner
3015 if msg.strip() != u'':
3016
3017 login = gmPG2.get_default_login()
3018 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
3019 login.database,
3020 gmTools.coalesce(login.host, u'localhost')
3021 ))
3022 msg = auth + msg + u'\n\n'
3023
3024 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3025 None,
3026
3027 -1,
3028 caption = _('Verifying database'),
3029 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3030 button_defs = [
3031 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3032 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3033 ]
3034 )
3035 go_on = dlg.ShowModal()
3036 dlg.Destroy()
3037 if go_on != wx.ID_YES:
3038 _log.info('user decided to not connect to this database')
3039 return False
3040
3041
3042 self.__check_db_lang()
3043
3044 return True
3045
3047 """Setup access to a config file for storing preferences."""
3048
3049 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3050
3051 candidates = []
3052 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3053 if explicit_file is not None:
3054 candidates.append(explicit_file)
3055
3056 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3057 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3058 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3059
3060 prefs_file = None
3061 for candidate in candidates:
3062 try:
3063 open(candidate, 'a+').close()
3064 prefs_file = candidate
3065 break
3066 except IOError:
3067 continue
3068
3069 if prefs_file is None:
3070 msg = _(
3071 'Cannot find configuration file in any of:\n'
3072 '\n'
3073 ' %s\n'
3074 'You may need to use the comand line option\n'
3075 '\n'
3076 ' --conf-file=<FILE>'
3077 ) % '\n '.join(candidates)
3078 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3079 return False
3080
3081 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3082 _log.info('user preferences file: %s', prefs_file)
3083
3084 return True
3085
3087
3088 from socket import error as SocketError
3089 from Gnumed.pycommon import gmScriptingListener
3090 from Gnumed.wxpython import gmMacro
3091
3092 slave_personality = gmTools.coalesce (
3093 _cfg.get (
3094 group = u'workplace',
3095 option = u'slave personality',
3096 source_order = [
3097 ('explicit', 'return'),
3098 ('workbase', 'return'),
3099 ('user', 'return'),
3100 ('system', 'return')
3101 ]
3102 ),
3103 u'gnumed-client'
3104 )
3105 _cfg.set_option(option = 'slave personality', value = slave_personality)
3106
3107
3108 port = int (
3109 gmTools.coalesce (
3110 _cfg.get (
3111 group = u'workplace',
3112 option = u'xml-rpc port',
3113 source_order = [
3114 ('explicit', 'return'),
3115 ('workbase', 'return'),
3116 ('user', 'return'),
3117 ('system', 'return')
3118 ]
3119 ),
3120 9999
3121 )
3122 )
3123 _cfg.set_option(option = 'xml-rpc port', value = port)
3124
3125 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3126 global _scripting_listener
3127 try:
3128 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3129 except SocketError, e:
3130 _log.exception('cannot start GNUmed XML-RPC server')
3131 gmGuiHelpers.gm_show_error (
3132 aMessage = (
3133 'Cannot start the GNUmed server:\n'
3134 '\n'
3135 ' [%s]'
3136 ) % e,
3137 aTitle = _('GNUmed startup')
3138 )
3139 return False
3140
3141 return True
3142
3163
3165 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3166 _log.warning("system locale is undefined (probably meaning 'C')")
3167 return True
3168
3169
3170 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3171 db_lang = rows[0]['lang']
3172
3173 if db_lang is None:
3174 _log.debug("database locale currently not set")
3175 msg = _(
3176 "There is no language selected in the database for user [%s].\n"
3177 "Your system language is currently set to [%s].\n\n"
3178 "Do you want to set the database language to '%s' ?\n\n"
3179 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3180 checkbox_msg = _('Remember to ignore missing language')
3181 else:
3182 _log.debug("current database locale: [%s]" % db_lang)
3183 msg = _(
3184 "The currently selected database language ('%s') does\n"
3185 "not match the current system language ('%s').\n"
3186 "\n"
3187 "Do you want to set the database language to '%s' ?\n"
3188 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3189 checkbox_msg = _('Remember to ignore language mismatch')
3190
3191
3192 if db_lang == gmI18N.system_locale_level['full']:
3193 _log.debug('Database locale (%s) up to date.' % db_lang)
3194 return True
3195 if db_lang == gmI18N.system_locale_level['country']:
3196 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3197 return True
3198 if db_lang == gmI18N.system_locale_level['language']:
3199 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3200 return True
3201
3202 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3203
3204
3205 ignored_sys_lang = _cfg.get (
3206 group = u'backend',
3207 option = u'ignored mismatching system locale',
3208 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3209 )
3210
3211
3212 if gmI18N.system_locale == ignored_sys_lang:
3213 _log.info('configured to ignore system-to-database locale mismatch')
3214 return True
3215
3216
3217 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3218 None,
3219 -1,
3220 caption = _('Checking database language settings'),
3221 question = msg,
3222 button_defs = [
3223 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3224 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3225 ],
3226 show_checkbox = True,
3227 checkbox_msg = checkbox_msg,
3228 checkbox_tooltip = _(
3229 'Checking this will make GNUmed remember your decision\n'
3230 'until the system language is changed.\n'
3231 '\n'
3232 'You can also reactivate this inquiry by removing the\n'
3233 'corresponding "ignore" option from the configuration file\n'
3234 '\n'
3235 ' [%s]'
3236 ) % _cfg.get(option = 'user_preferences_file')
3237 )
3238 decision = dlg.ShowModal()
3239 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3240 dlg.Destroy()
3241
3242 if decision == wx.ID_NO:
3243 if not remember_ignoring_problem:
3244 return True
3245 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3246 gmCfg2.set_option_in_INI_file (
3247 filename = _cfg.get(option = 'user_preferences_file'),
3248 group = 'backend',
3249 option = 'ignored mismatching system locale',
3250 value = gmI18N.system_locale
3251 )
3252 return True
3253
3254
3255 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3256 if len(lang) > 0:
3257
3258
3259 rows, idx = gmPG2.run_rw_queries (
3260 link_obj = None,
3261 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3262 return_data = True
3263 )
3264 if rows[0][0]:
3265 _log.debug("Successfully set database language to [%s]." % lang)
3266 else:
3267 _log.error('Cannot set database language to [%s].' % lang)
3268 continue
3269 return True
3270
3271
3272 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3273 gmPG2.run_rw_queries(queries = [{
3274 'cmd': u'select i18n.force_curr_lang(%s)',
3275 'args': [gmI18N.system_locale_level['country']]
3276 }])
3277
3278 return True
3279
3281 try:
3282 kwargs['originated_in_database']
3283 print '==> got notification from database "%s":' % kwargs['signal']
3284 except KeyError:
3285 print '==> received signal from client: "%s"' % kwargs['signal']
3286
3287 del kwargs['signal']
3288 for key in kwargs.keys():
3289
3290 try: print ' [%s]: %s' % (key, kwargs[key])
3291 except: print 'cannot print signal information'
3292
3294
3295 try:
3296 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3297 print ' data: %s' % msg.data
3298 print msg
3299 except: print 'problem printing pubsub message information'
3300
3302
3303 if _cfg.get(option = 'debug'):
3304 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3305 _log.debug('gmDispatcher signal monitor activated')
3306 wx.lib.pubsub.Publisher().subscribe (
3307 listener = _signal_debugging_monitor_pubsub
3308
3309 )
3310 _log.debug('wx.lib.pubsub signal monitor activated')
3311
3312 wx.InitAllImageHandlers()
3313
3314
3315
3316 app = gmApp(redirect = False, clearSigInt = False)
3317 app.MainLoop()
3318
3319
3320
3321 if __name__ == '__main__':
3322
3323 from GNUmed.pycommon import gmI18N
3324 gmI18N.activate_locale()
3325 gmI18N.install_domain()
3326
3327 _log.info('Starting up as main module.')
3328 main()
3329
3330
3331