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 self.__setup_font()
103
104 self.__gb = gmGuiBroker.GuiBroker()
105 self.__pre_exit_callbacks = []
106 self.bar_width = -1
107 self.menu_id2plugin = {}
108
109 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
110
111 self.__setup_main_menu()
112 self.setup_statusbar()
113 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
114 gmTools.coalesce(_provider['title'], ''),
115 _provider['firstnames'][:1],
116 _provider['lastnames'],
117 _provider['short_alias'],
118 _provider['db_user']
119 ))
120
121 self.__set_window_title_template()
122 self.__update_window_title()
123
124
125
126
127
128 self.SetIcon(gmTools.get_icon(wx = wx))
129
130 self.__register_events()
131
132 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
133 self.vbox = wx.BoxSizer(wx.VERTICAL)
134 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
135
136 self.SetAutoLayout(True)
137 self.SetSizerAndFit(self.vbox)
138
139
140
141
142
143 self.__set_GUI_size()
144
145
147
148 font = self.GetFont()
149 _log.debug('system default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
150
151 desired_font_face = _cfg.get (
152 group = u'workplace',
153 option = u'client font',
154 source_order = [
155 ('explicit', 'return'),
156 ('workbase', 'return'),
157 ('local', 'return'),
158 ('user', 'return'),
159 ('system', 'return')
160 ]
161 )
162
163 fonts2try = []
164 if desired_font_face is not None:
165 _log.info('client is configured to use font [%s]', desired_font_face)
166 fonts2try.append(desired_font_face)
167
168 if wx.Platform == '__WXMSW__':
169 sane_font_face = u'DejaVu Sans'
170 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face)
171 fonts2try.append(sane_font_face)
172
173 if len(fonts2try) == 0:
174 return
175
176 for font_face in fonts2try:
177 success = font.SetFaceName(font_face)
178 if success:
179 self.SetFont(font)
180 _log.debug('switched font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
181 return
182 font = self.GetFont()
183 _log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face)
184
185 return
186
188 """Try to get previous window size from backend."""
189
190 cfg = gmCfg.cCfgSQL()
191
192
193 width = int(cfg.get2 (
194 option = 'main.window.width',
195 workplace = gmSurgery.gmCurrentPractice().active_workplace,
196 bias = 'workplace',
197 default = 800
198 ))
199
200
201 height = int(cfg.get2 (
202 option = 'main.window.height',
203 workplace = gmSurgery.gmCurrentPractice().active_workplace,
204 bias = 'workplace',
205 default = 600
206 ))
207
208 dw = wx.DisplaySize()[0]
209 dh = wx.DisplaySize()[1]
210
211 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
212 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
213 _log.debug('previous GUI size [%s:%s]', width, height)
214
215
216 if width > dw:
217 _log.debug('adjusting GUI width from %s to %s', width, dw)
218 width = dw
219
220 if height > dh:
221 _log.debug('adjusting GUI height from %s to %s', height, dh)
222 height = dh
223
224
225 if width < 100:
226 _log.debug('adjusting GUI width to minimum of 100 pixel')
227 width = 100
228 if height < 100:
229 _log.debug('adjusting GUI height to minimum of 100 pixel')
230 height = 100
231
232 _log.info('setting GUI to size [%s:%s]', width, height)
233
234 self.SetClientSize(wx.Size(width, height))
235
237 """Create the main menu entries.
238
239 Individual entries are farmed out to the modules.
240
241 menu item template:
242
243 item = menu_emr_edit.Append(-1, _(''), _(''))
244 self.Bind(wx.EVT_MENU, self__on_, item)
245 """
246 global wx
247 self.mainmenu = wx.MenuBar()
248 self.__gb['main.mainmenu'] = self.mainmenu
249
250
251 menu_gnumed = wx.Menu()
252
253 self.menu_plugins = wx.Menu()
254 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
255
256 ID = wx.NewId()
257 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
258 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
259
260 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
261 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
262
263
264 menu_gnumed.AppendSeparator()
265
266
267 menu_config = wx.Menu()
268
269 item = menu_config.Append(-1, _('List configuration'), _('List all configuration items stored in the database.'))
270 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
271
272
273 menu_cfg_db = wx.Menu()
274
275 ID = wx.NewId()
276 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
277 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
278
279 ID = wx.NewId()
280 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
281 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
282
283 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
284
285
286 menu_cfg_client = wx.Menu()
287
288 ID = wx.NewId()
289 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
290 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
291
292
293
294
295
296 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
297 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
298
299 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
300
301
302 menu_cfg_ui = wx.Menu()
303
304
305 menu_cfg_doc = wx.Menu()
306
307 ID = wx.NewId()
308 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
309 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
310
311 ID = wx.NewId()
312 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
313 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
314
315 ID = wx.NewId()
316 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
317 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
318
319 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
320
321
322 menu_cfg_update = wx.Menu()
323
324 ID = wx.NewId()
325 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
326 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
327
328 ID = wx.NewId()
329 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
330 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
331
332 ID = wx.NewId()
333 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
334 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
335
336 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
337
338
339 menu_cfg_pat_search = wx.Menu()
340
341 ID = wx.NewId()
342 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
343 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
344
345 ID = wx.NewId()
346 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
347 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
348
349 ID = wx.NewId()
350 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
351 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
352
353 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
354 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
355
356 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
357 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
358
359 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
360
361
362 menu_cfg_soap_editing = wx.Menu()
363
364 ID = wx.NewId()
365 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
366 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
367
368 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.'))
369 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item)
370
371 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
372
373 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
374
375
376 menu_cfg_ext_tools = wx.Menu()
377
378
379
380
381
382 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
383 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
384
385 ID = wx.NewId()
386 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
387 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
388
389 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
390 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
391
392 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
393 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
394
395 item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.'))
396 self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item)
397
398 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
399 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
400
401 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
402 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
403
404 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
405 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
406
407 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
408 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
409
410 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
411
412
413 menu_cfg_emr = wx.Menu()
414
415 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
416 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
417
418 item = menu_cfg_emr.Append(-1, _('Primary doctor'), _('Select the primary doctor to fall back to for patients without one.'))
419 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
420
421
422 menu_cfg_encounter = wx.Menu()
423
424 ID = wx.NewId()
425 menu_cfg_encounter.Append(ID, _('Edit on patient change'), _('Edit encounter details on changing of patients.'))
426 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
427
428 ID = wx.NewId()
429 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
430 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
431
432 ID = wx.NewId()
433 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
434 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
435
436 ID = wx.NewId()
437 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
438 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
439
440 ID = wx.NewId()
441 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
442 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
443
444 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
445
446
447 menu_cfg_episode = wx.Menu()
448
449 ID = wx.NewId()
450 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
451 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
452
453 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
454 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
455 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
456
457
458 menu_master_data = wx.Menu()
459
460 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
461 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
462
463 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
464 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
465
466 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
467 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
468
469 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
470 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
471
472 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
473
474
475 menu_users = wx.Menu()
476
477 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
478 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
479
480 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
481 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
482
483 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
484 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
485
486 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
487
488
489 menu_gnumed.AppendSeparator()
490
491 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
492 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
493
494 self.mainmenu.Append(menu_gnumed, '&GNUmed')
495
496
497 menu_person = wx.Menu()
498
499 ID_CREATE_PATIENT = wx.NewId()
500 menu_person.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
501 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
502
503 ID_LOAD_EXT_PAT = wx.NewId()
504 menu_person.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
505 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
506
507 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
508 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
509
510 ID_DEL_PAT = wx.NewId()
511 menu_person.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
512 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
513
514 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
515 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
516
517 menu_person.AppendSeparator()
518
519 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
520 menu_person.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
521 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
522
523
524 ID = wx.NewId()
525 menu_person.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
526 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
527
528 menu_person.AppendSeparator()
529
530 self.mainmenu.Append(menu_person, '&Person')
531 self.__gb['main.patientmenu'] = menu_person
532
533
534 menu_emr = wx.Menu()
535
536
537 menu_emr_show = wx.Menu()
538
539 item = menu_emr_show.Append(-1, _('Summary'), _('Show a high-level summary of the EMR.'))
540 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
541
542 menu_emr.AppendMenu(wx.NewId(), _('Show as ...'), menu_emr_show)
543 self.__gb['main.emr_showmenu'] = menu_emr_show
544
545
546 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
547 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
548
549 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
550 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
551
552
553 menu_emr_edit = wx.Menu()
554
555 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'))
556 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
557
558 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
559 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
560
561 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
562 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
563
564 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
565 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
566
567 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
568 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
569
570 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
571 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
572
573 item = menu_emr_edit.Append(-1, _('&Measurement(s)'), _('Add (a) measurement result(s) for the current patient.'))
574 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
575
576 item = menu_emr_edit.Append(-1, _('&Vaccination(s)'), _('Add (a) vaccination(s) for the current patient.'))
577 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
578
579 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
580
581
582
583 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
584 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
585
586 item = menu_emr.Append(-1, _('&Encounters list'), _('List all encounters including empty ones.'))
587 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
588
589 menu_emr.AppendSeparator()
590
591
592 menu_emr_export = wx.Menu()
593
594 ID_EXPORT_EMR_ASCII = wx.NewId()
595 menu_emr_export.Append (
596 ID_EXPORT_EMR_ASCII,
597 _('Text document'),
598 _("Export the EMR of the active patient into a text file")
599 )
600 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
601
602 ID_EXPORT_EMR_JOURNAL = wx.NewId()
603 menu_emr_export.Append (
604 ID_EXPORT_EMR_JOURNAL,
605 _('Journal'),
606 _("Export the EMR of the active patient as a chronological journal into a text file")
607 )
608 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
609
610 ID_EXPORT_MEDISTAR = wx.NewId()
611 menu_emr_export.Append (
612 ID_EXPORT_MEDISTAR,
613 _('MEDISTAR import format'),
614 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
615 )
616 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
617
618 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
619
620 menu_emr.AppendSeparator()
621
622 self.mainmenu.Append(menu_emr, _("&EMR"))
623 self.__gb['main.emrmenu'] = menu_emr
624
625
626 menu_paperwork = wx.Menu()
627
628 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
629 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
630
631 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
632
633
634 self.menu_tools = wx.Menu()
635
636 ID_DICOM_VIEWER = wx.NewId()
637 viewer = _('no viewer installed')
638 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
639 viewer = u'OsiriX'
640 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
641 viewer = u'Aeskulap'
642 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
643 viewer = u'AMIDE'
644 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
645 viewer = u'DicomScope'
646 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
647 viewer = u'(x)medcon'
648 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)
649 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
650 if viewer == _('no viewer installed'):
651 _log.info('neither of OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
652 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
653
654
655
656
657
658 ID = wx.NewId()
659 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
660 wx.EVT_MENU(self, ID, self.__on_snellen)
661
662 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
663 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
664
665 item = self.menu_tools.Append(-1, _('arriba'), _('arriba: cardiovascular risk assessment (%s).') % u'www.arriba-hausarzt.de')
666 self.Bind(wx.EVT_MENU, self.__on_arriba, item)
667
668 self.menu_tools.AppendSeparator()
669
670 self.mainmenu.Append(self.menu_tools, _("&Tools"))
671 self.__gb['main.toolsmenu'] = self.menu_tools
672
673
674 menu_knowledge = wx.Menu()
675
676
677 menu_drug_dbs = wx.Menu()
678
679 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
680 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
681
682
683
684
685
686
687 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
688
689 menu_id = wx.NewId()
690 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
691 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
692
693
694
695
696 ID_MEDICAL_LINKS = wx.NewId()
697 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
698 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
699
700 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
701 self.__gb['main.knowledgemenu'] = menu_knowledge
702
703
704 self.menu_office = wx.Menu()
705
706 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
707 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
708
709 self.menu_office.AppendSeparator()
710
711 self.mainmenu.Append(self.menu_office, _('&Office'))
712 self.__gb['main.officemenu'] = self.menu_office
713
714
715 help_menu = wx.Menu()
716
717 ID = wx.NewId()
718 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
719 wx.EVT_MENU(self, ID, self.__on_display_wiki)
720
721 ID = wx.NewId()
722 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
723 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
724
725 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
726 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
727
728 menu_debugging = wx.Menu()
729
730 ID_SCREENSHOT = wx.NewId()
731 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
732 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
733
734 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
735 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
736
737 ID = wx.NewId()
738 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
739 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
740
741 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
742 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
743
744 ID = wx.NewId()
745 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
746 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
747
748 ID_UNBLOCK = wx.NewId()
749 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
750 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
751
752 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
753 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
754
755
756
757
758 if _cfg.get(option = 'debug'):
759 ID_TOGGLE_PAT_LOCK = wx.NewId()
760 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient'), _('Lock/unlock patient - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
761 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
762
763 ID_TEST_EXCEPTION = wx.NewId()
764 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
765 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
766
767 ID = wx.NewId()
768 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
769 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
770 try:
771 import wx.lib.inspection
772 except ImportError:
773 menu_debugging.Enable(id = ID, enable = False)
774
775 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
776
777 help_menu.AppendSeparator()
778
779 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
780 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
781
782 ID_CONTRIBUTORS = wx.NewId()
783 help_menu.Append(ID_CONTRIBUTORS, _('GNUmed contributors'), _('show GNUmed contributors'))
784 wx.EVT_MENU(self, ID_CONTRIBUTORS, self.__on_show_contributors)
785
786 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
787 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
788
789 help_menu.AppendSeparator()
790
791 self.mainmenu.Append(help_menu, _("&Help"))
792
793 self.__gb['main.helpmenu'] = help_menu
794
795
796 self.SetMenuBar(self.mainmenu)
797
800
801
802
804 """register events we want to react to"""
805
806 wx.EVT_CLOSE(self, self.OnClose)
807 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
808 wx.EVT_END_SESSION(self, self._on_end_session)
809
810 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
811 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
812 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
813 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
814 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
815 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
816 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
817 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
818
819 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
820
821 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
822
823 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
824
825 _log.debug('registering plugin with menu system')
826 _log.debug(' generic name: %s', plugin_name)
827 _log.debug(' class name: %s', class_name)
828 _log.debug(' specific menu: %s', menu_name)
829 _log.debug(' menu item: %s', menu_item_name)
830
831
832 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
833 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
834 self.menu_id2plugin[item.Id] = class_name
835
836
837 if menu_name is not None:
838 menu = self.__gb['main.%smenu' % menu_name]
839 item = menu.Append(-1, menu_item_name, menu_help_string)
840 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
841 self.menu_id2plugin[item.Id] = class_name
842
843 return True
844
846 gmDispatcher.send (
847 signal = u'display_widget',
848 name = self.menu_id2plugin[evt.Id]
849 )
850
852 wx.Bell()
853 wx.Bell()
854 wx.Bell()
855 _log.warning('unhandled event detected: QUERY_END_SESSION')
856 _log.info('we should be saving ourselves from here')
857 gmLog2.flush()
858 print "unhandled event detected: QUERY_END_SESSION"
859
861 wx.Bell()
862 wx.Bell()
863 wx.Bell()
864 _log.warning('unhandled event detected: END_SESSION')
865 gmLog2.flush()
866 print "unhandled event detected: END_SESSION"
867
869 if not callable(callback):
870 raise TypeError(u'callback [%s] not callable' % callback)
871
872 self.__pre_exit_callbacks.append(callback)
873
874 - def _on_set_statustext_pubsub(self, context=None):
875 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
876 wx.CallAfter(self.SetStatusText, msg)
877
878 try:
879 if context.data['beep']:
880 wx.Bell()
881 except KeyError:
882 pass
883
884 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
885
886 if msg is None:
887 msg = _('programmer forgot to specify status message')
888
889 if loglevel is not None:
890 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
891
892 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
893 wx.CallAfter(self.SetStatusText, msg)
894
895 if beep:
896 wx.Bell()
897
899 wx.CallAfter(self.__on_db_maintenance_warning)
900
902
903 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
904 wx.Bell()
905 if not wx.GetApp().IsActive():
906 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
907
908 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
909
910 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
911 None,
912 -1,
913 caption = _('Database shutdown warning'),
914 question = _(
915 'The database will be shut down for maintenance\n'
916 'in a few minutes.\n'
917 '\n'
918 'In order to not suffer any loss of data you\n'
919 'will need to save your current work and log\n'
920 'out of this GNUmed client.\n'
921 ),
922 button_defs = [
923 {
924 u'label': _('Close now'),
925 u'tooltip': _('Close this GNUmed client immediately.'),
926 u'default': False
927 },
928 {
929 u'label': _('Finish work'),
930 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
931 u'default': True
932 }
933 ]
934 )
935 decision = dlg.ShowModal()
936 if decision == wx.ID_YES:
937 top_win = wx.GetApp().GetTopWindow()
938 wx.CallAfter(top_win.Close)
939
941 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
942
944
945 if not wx.GetApp().IsActive():
946 if urgent:
947 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
948 else:
949 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
950
951 if msg is not None:
952 self.SetStatusText(msg)
953
954 if urgent:
955 wx.Bell()
956
957 gmHooks.run_hook_script(hook = u'request_user_attention')
958
960 wx.CallAfter(self.__on_pat_name_changed)
961
963 self.__update_window_title()
964
966 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
967
969 self.__update_window_title()
970 try:
971 gmHooks.run_hook_script(hook = u'post_patient_activation')
972 except:
973 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
974 raise
975
977 return self.__sanity_check_encounter()
978
1035
1036
1037
1040
1048
1049
1050
1065
1088
1090 from Gnumed.wxpython import gmAbout
1091 contribs = gmAbout.cContributorsDlg (
1092 parent = self,
1093 id = -1,
1094 title = _('GNUmed contributors'),
1095 size = wx.Size(400,600),
1096 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1097 )
1098 contribs.ShowModal()
1099 del contribs
1100 del gmAbout
1101
1102
1103
1105 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1106 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1107 self.Close(True)
1108 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1109
1112
1114 send = gmGuiHelpers.gm_show_question (
1115 _('This will send a notification about database downtime\n'
1116 'to all GNUmed clients connected to your database.\n'
1117 '\n'
1118 'Do you want to send the notification ?\n'
1119 ),
1120 _('Announcing database maintenance downtime')
1121 )
1122 if not send:
1123 return
1124 gmPG2.send_maintenance_notification()
1125
1126
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1176
1177 gmCfgWidgets.configure_string_option (
1178 message = _(
1179 'Some network installations cannot cope with loading\n'
1180 'documents of arbitrary size in one piece from the\n'
1181 'database (mainly observed on older Windows versions)\n.'
1182 '\n'
1183 'Under such circumstances documents need to be retrieved\n'
1184 'in chunks and reassembled on the client.\n'
1185 '\n'
1186 'Here you can set the size (in Bytes) above which\n'
1187 'GNUmed will retrieve documents in chunks. Setting this\n'
1188 'value to 0 will disable the chunking protocol.'
1189 ),
1190 option = 'horstspace.blob_export_chunk_size',
1191 bias = 'workplace',
1192 default_value = 1024 * 1024,
1193 validator = is_valid
1194 )
1195
1196
1197
1265
1269
1270
1271
1280
1281 gmCfgWidgets.configure_string_option (
1282 message = _(
1283 'When GNUmed cannot find an OpenOffice server it\n'
1284 'will try to start one. OpenOffice, however, needs\n'
1285 'some time to fully start up.\n'
1286 '\n'
1287 'Here you can set the time for GNUmed to wait for OOo.\n'
1288 ),
1289 option = 'external.ooo.startup_settle_time',
1290 bias = 'workplace',
1291 default_value = 2.0,
1292 validator = is_valid
1293 )
1294
1297
1312
1313 gmCfgWidgets.configure_string_option (
1314 message = _(
1315 'GNUmed will use this URL to access a website which lets\n'
1316 'you report an adverse drug reaction (ADR).\n'
1317 '\n'
1318 'If you leave this empty it will fall back\n'
1319 'to an URL for reporting ADRs in Germany.'
1320 ),
1321 option = 'external.urls.report_ADR',
1322 bias = 'user',
1323 default_value = german_default,
1324 validator = is_valid
1325 )
1326
1340
1341 gmCfgWidgets.configure_string_option (
1342 message = _(
1343 'GNUmed will use this URL to access a website which lets\n'
1344 'you report an adverse vaccination reaction (vADR).\n'
1345 '\n'
1346 'If you set it to a specific address that URL must be\n'
1347 'accessible now. If you leave it empty it will fall back\n'
1348 'to the URL for reporting other adverse drug reactions.'
1349 ),
1350 option = 'external.urls.report_vaccine_ADR',
1351 bias = 'user',
1352 default_value = german_default,
1353 validator = is_valid
1354 )
1355
1369
1370 gmCfgWidgets.configure_string_option (
1371 message = _(
1372 'GNUmed will use this URL to access an encyclopedia of\n'
1373 'measurement/lab methods from within the measurments grid.\n'
1374 '\n'
1375 'You can leave this empty but to set it to a specific\n'
1376 'address the URL must be accessible now.'
1377 ),
1378 option = 'external.urls.measurements_encyclopedia',
1379 bias = 'user',
1380 default_value = german_default,
1381 validator = is_valid
1382 )
1383
1397
1398 gmCfgWidgets.configure_string_option (
1399 message = _(
1400 'GNUmed will use this URL to access a page showing\n'
1401 'vaccination schedules.\n'
1402 '\n'
1403 'You can leave this empty but to set it to a specific\n'
1404 'address the URL must be accessible now.'
1405 ),
1406 option = 'external.urls.vaccination_plans',
1407 bias = 'user',
1408 default_value = german_default,
1409 validator = is_valid
1410 )
1411
1424
1425 gmCfgWidgets.configure_string_option (
1426 message = _(
1427 'Enter the shell command with which to start the\n'
1428 'the ACS risk assessment calculator.\n'
1429 '\n'
1430 'GNUmed will try to verify the path which may,\n'
1431 'however, fail if you are using an emulator such\n'
1432 'as Wine. Nevertheless, starting the calculator\n'
1433 'will work as long as the shell command is correct\n'
1434 'despite the failing test.'
1435 ),
1436 option = 'external.tools.acs_risk_calculator_cmd',
1437 bias = 'user',
1438 validator = is_valid
1439 )
1440
1443
1456
1457 gmCfgWidgets.configure_string_option (
1458 message = _(
1459 'Enter the shell command with which to start\n'
1460 'the FreeDiams drug database frontend.\n'
1461 '\n'
1462 'GNUmed will try to verify that path.'
1463 ),
1464 option = 'external.tools.freediams_cmd',
1465 bias = 'workplace',
1466 default_value = None,
1467 validator = is_valid
1468 )
1469
1482
1483 gmCfgWidgets.configure_string_option (
1484 message = _(
1485 'Enter the shell command with which to start the\n'
1486 'the IFAP drug database.\n'
1487 '\n'
1488 'GNUmed will try to verify the path which may,\n'
1489 'however, fail if you are using an emulator such\n'
1490 'as Wine. Nevertheless, starting IFAP will work\n'
1491 'as long as the shell command is correct despite\n'
1492 'the failing test.'
1493 ),
1494 option = 'external.ifap-win.shell_command',
1495 bias = 'workplace',
1496 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1497 validator = is_valid
1498 )
1499
1500
1501
1550
1551
1552
1569
1572
1575
1580
1581 gmCfgWidgets.configure_string_option (
1582 message = _(
1583 'When a patient is activated GNUmed checks the\n'
1584 "proximity of the patient's birthday.\n"
1585 '\n'
1586 'If the birthday falls within the range of\n'
1587 ' "today %s <the interval you set here>"\n'
1588 'GNUmed will remind you of the recent or\n'
1589 'imminent anniversary.'
1590 ) % u'\u2213',
1591 option = u'patient_search.dob_warn_interval',
1592 bias = 'user',
1593 default_value = '1 week',
1594 validator = is_valid
1595 )
1596
1598
1599 gmCfgWidgets.configure_boolean_option (
1600 parent = self,
1601 question = _(
1602 'When adding progress notes do you want to\n'
1603 'allow opening several unassociated, new\n'
1604 'episodes for a patient at once ?\n'
1605 '\n'
1606 'This can be particularly helpful when entering\n'
1607 'progress notes on entirely new patients presenting\n'
1608 'with a multitude of problems on their first visit.'
1609 ),
1610 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1611 button_tooltips = [
1612 _('Yes, allow for multiple new episodes concurrently.'),
1613 _('No, only allow editing one new episode at a time.')
1614 ]
1615 )
1616
1618
1619 gmCfgWidgets.configure_boolean_option (
1620 parent = self,
1621 question = _(
1622 'When activating a patient, do you want GNUmed to\n'
1623 'auto-open editors for all active problems that were\n'
1624 'touched upon during the current and the most recent\n'
1625 'encounter ?'
1626 ),
1627 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1628 button_tooltips = [
1629 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1630 _('No, only auto-open one editor for a new, unassociated problem.')
1631 ]
1632 )
1633
1679
1680
1681
1684
1687
1701
1703 gmCfgWidgets.configure_boolean_option (
1704 parent = self,
1705 question = _(
1706 'Do you want GNUmed to show the encounter\n'
1707 'details editor when changing the active patient ?'
1708 ),
1709 option = 'encounter.show_editor_before_patient_change',
1710 button_tooltips = [
1711 _('Yes, show the encounter editor if it seems appropriate.'),
1712 _('No, never show the encounter editor even if it would seem useful.')
1713 ]
1714 )
1715
1720
1721 gmCfgWidgets.configure_string_option (
1722 message = _(
1723 'When a patient is activated GNUmed checks the\n'
1724 'chart for encounters lacking any entries.\n'
1725 '\n'
1726 'Any such encounters older than what you set\n'
1727 'here will be removed from the medical record.\n'
1728 '\n'
1729 'To effectively disable removal of such encounters\n'
1730 'set this option to an improbable value.\n'
1731 ),
1732 option = 'encounter.ttl_if_empty',
1733 bias = 'user',
1734 default_value = '1 week',
1735 validator = is_valid
1736 )
1737
1742
1743 gmCfgWidgets.configure_string_option (
1744 message = _(
1745 'When a patient is activated GNUmed checks the\n'
1746 'age of the most recent encounter.\n'
1747 '\n'
1748 'If that encounter is younger than this age\n'
1749 'the existing encounter will be continued.\n'
1750 '\n'
1751 '(If it is really old a new encounter is\n'
1752 ' started, or else GNUmed will ask you.)\n'
1753 ),
1754 option = 'encounter.minimum_ttl',
1755 bias = 'user',
1756 default_value = '1 hour 30 minutes',
1757 validator = is_valid
1758 )
1759
1764
1765 gmCfgWidgets.configure_string_option (
1766 message = _(
1767 'When a patient is activated GNUmed checks the\n'
1768 'age of the most recent encounter.\n'
1769 '\n'
1770 'If that encounter is older than this age\n'
1771 'GNUmed will always start a new encounter.\n'
1772 '\n'
1773 '(If it is very recent the existing encounter\n'
1774 ' is continued, or else GNUmed will ask you.)\n'
1775 ),
1776 option = 'encounter.maximum_ttl',
1777 bias = 'user',
1778 default_value = '6 hours',
1779 validator = is_valid
1780 )
1781
1790
1791 gmCfgWidgets.configure_string_option (
1792 message = _(
1793 'At any time there can only be one open (ongoing)\n'
1794 'episode for each health issue.\n'
1795 '\n'
1796 'When you try to open (add data to) an episode on a health\n'
1797 'issue GNUmed will check for an existing open episode on\n'
1798 'that issue. If there is any it will check the age of that\n'
1799 'episode. The episode is closed if it has been dormant (no\n'
1800 'data added, that is) for the period of time (in days) you\n'
1801 'set here.\n'
1802 '\n'
1803 "If the existing episode hasn't been dormant long enough\n"
1804 'GNUmed will consult you what to do.\n'
1805 '\n'
1806 'Enter maximum episode dormancy in DAYS:'
1807 ),
1808 option = 'episode.ttl',
1809 bias = 'user',
1810 default_value = 60,
1811 validator = is_valid
1812 )
1813
1844
1859
1884
1896
1897 gmCfgWidgets.configure_string_option (
1898 message = _(
1899 'GNUmed can check for new releases being available. To do\n'
1900 'so it needs to load version information from an URL.\n'
1901 '\n'
1902 'The default URL is:\n'
1903 '\n'
1904 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1905 '\n'
1906 'but you can configure any other URL locally. Note\n'
1907 'that you must enter the location as a valid URL.\n'
1908 'Depending on the URL the client will need online\n'
1909 'access when checking for updates.'
1910 ),
1911 option = u'horstspace.update.url',
1912 bias = u'workplace',
1913 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1914 validator = is_valid
1915 )
1916
1934
1951
1962
1963 gmCfgWidgets.configure_string_option (
1964 message = _(
1965 'GNUmed can show the document review dialog after\n'
1966 'calling the appropriate viewer for that document.\n'
1967 '\n'
1968 'Select the conditions under which you want\n'
1969 'GNUmed to do so:\n'
1970 '\n'
1971 ' 0: never display the review dialog\n'
1972 ' 1: always display the dialog\n'
1973 ' 2: only if there is no previous review by me\n'
1974 '\n'
1975 'Note that if a viewer is configured to not block\n'
1976 'GNUmed during document display the review dialog\n'
1977 'will actually appear in parallel to the viewer.'
1978 ),
1979 option = u'horstspace.document_viewer.review_after_display',
1980 bias = u'user',
1981 default_value = 2,
1982 validator = is_valid
1983 )
1984
1986
1987
1988 master_data_lists = [
1989 'adr',
1990 'drugs',
1991 'codes',
1992 'substances_in_brands',
1993 'substances',
1994 'labs',
1995 'form_templates',
1996 'doc_types',
1997 'enc_types',
1998 'text_expansions',
1999 'meta_test_types',
2000
2001 'patient_tags',
2002 'provinces',
2003 'db_translations',
2004 'test_types',
2005 'org_units',
2006 'vacc_indications',
2007 'vaccines',
2008 'workplaces'
2009 ]
2010
2011 master_data_list_names = {
2012 'adr': _('Addresses (likely slow)'),
2013 'drugs': _('Branded drugs (as marketed)'),
2014 'codes': _('Codes and their respective terms'),
2015 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2016 'labs': _('Diagnostic organizations (path labs, ...)'),
2017 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2018 'doc_types': _('Document types'),
2019 'enc_types': _('Encounter types'),
2020 'text_expansions': _('Keyword based text expansion macros'),
2021 'meta_test_types': _('Meta test/measurement types'),
2022
2023 'patient_tags': _('Patient tags'),
2024 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2025 'db_translations': _('String translations in the database'),
2026 'test_types': _('Test/measurement types'),
2027 'org_units': _('Units of organizations (branches, sites, departments, parts, ...'),
2028 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2029 'vaccines': _('Vaccines'),
2030 'workplaces': _('Workplace profiles (which plugins to load)'),
2031 'substances': _('Consumable substances')
2032 }
2033
2034 map_list2handler = {
2035 'org_units': gmOrganizationWidgets.manage_org_units,
2036 'form_templates': gmFormWidgets.manage_form_templates,
2037 'doc_types': gmDocumentWidgets.manage_document_types,
2038 'text_expansions': gmProviderInboxWidgets.configure_keyword_text_expansion,
2039 'db_translations': gmI18nWidgets.manage_translations,
2040 'codes': gmCodingWidgets.browse_coded_terms,
2041 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2042 'provinces': gmPersonContactWidgets.manage_provinces,
2043 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins,
2044 'drugs': gmMedicationWidgets.manage_branded_drugs,
2045 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2046 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2047 'test_types': gmMeasurementWidgets.manage_measurement_types,
2048 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2049 'vaccines': gmVaccWidgets.manage_vaccines,
2050 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2051
2052 'adr': gmPersonContactWidgets.manage_addresses,
2053 'substances': gmMedicationWidgets.manage_consumable_substances,
2054 'patient_tags': gmDemographicsWidgets.manage_tag_images
2055 }
2056
2057
2058 def edit(item):
2059 try: map_list2handler[item](parent = self)
2060 except KeyError: pass
2061 return False
2062
2063
2064 gmListWidgets.get_choices_from_list (
2065 parent = self,
2066 caption = _('Master data management'),
2067 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2068 data = master_data_lists,
2069 columns = [_('Select the list you want to manage:')],
2070 edit_callback = edit,
2071 single_selection = True,
2072 ignore_OK_button = True
2073 )
2074
2088
2090 curr_pat = gmPerson.gmCurrentPatient()
2091
2092 arriba = gmArriba.cArriba()
2093 if not arriba.run(patient = curr_pat, debug = _cfg.get(option = 'debug')):
2094 return
2095
2096
2097 if curr_pat is None:
2098 return
2099
2100 if arriba.pdf_result is None:
2101 return
2102
2103 doc = gmDocumentWidgets.save_file_as_new_document (
2104 parent = self,
2105 filename = arriba.pdf_result,
2106 document_type = _('risk assessment')
2107 )
2108
2109 try: os.remove(arriba.pdf_result)
2110 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2111
2112 if doc is None:
2113 return
2114
2115 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2116 doc.save()
2117
2118 try:
2119 open(arriba.xml_result).close()
2120 part = doc.add_part(file = arriba.xml_result)
2121 except StandardError:
2122 _log.exception('error accessing [%s]', arriba.xml_result)
2123 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2124
2125 if part is None:
2126 return
2127
2128 part['obj_comment'] = u'XML-Daten'
2129 part['filename'] = u'arriba-result.xml'
2130 part.save()
2131
2133
2134 dbcfg = gmCfg.cCfgSQL()
2135 cmd = dbcfg.get2 (
2136 option = u'external.tools.acs_risk_calculator_cmd',
2137 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2138 bias = 'user'
2139 )
2140
2141 if cmd is None:
2142 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2143 return
2144
2145 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
2146 try:
2147 subprocess.check_call (
2148 args = (cmd,),
2149 close_fds = True,
2150 cwd = cwd
2151 )
2152 except (OSError, ValueError, subprocess.CalledProcessError):
2153 _log.exception('there was a problem executing [%s]', cmd)
2154 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2155 return
2156
2157 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2158 for pdf in pdfs:
2159 try:
2160 open(pdf).close()
2161 except:
2162 _log.exception('error accessing [%s]', pdf)
2163 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2164 continue
2165
2166 doc = gmDocumentWidgets.save_file_as_new_document (
2167 parent = self,
2168 filename = pdf,
2169 document_type = u'risk assessment'
2170 )
2171
2172 try:
2173 os.remove(pdf)
2174 except StandardError:
2175 _log.exception('cannot remove [%s]', pdf)
2176
2177 if doc is None:
2178 continue
2179 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2180 doc.save()
2181
2182 return
2183
2185 dlg = gmSnellen.cSnellenCfgDlg()
2186 if dlg.ShowModal() != wx.ID_OK:
2187 return
2188
2189 frame = gmSnellen.cSnellenChart (
2190 width = dlg.vals[0],
2191 height = dlg.vals[1],
2192 alpha = dlg.vals[2],
2193 mirr = dlg.vals[3],
2194 parent = None
2195 )
2196 frame.CentreOnScreen(wx.BOTH)
2197
2198
2199 frame.Show(True)
2200
2201
2203 webbrowser.open (
2204 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
2205 new = False,
2206 autoraise = True
2207 )
2208
2211
2213 webbrowser.open (
2214 url = 'http://www.kompendium.ch',
2215 new = False,
2216 autoraise = True
2217 )
2218
2219
2220
2224
2225
2226
2228 wx.CallAfter(self.__save_screenshot)
2229 evt.Skip()
2230
2232
2233 time.sleep(0.5)
2234
2235 rect = self.GetRect()
2236
2237
2238 if sys.platform == 'linux2':
2239 client_x, client_y = self.ClientToScreen((0, 0))
2240 border_width = client_x - rect.x
2241 title_bar_height = client_y - rect.y
2242
2243 if self.GetMenuBar():
2244 title_bar_height /= 2
2245 rect.width += (border_width * 2)
2246 rect.height += title_bar_height + border_width
2247
2248 wdc = wx.ScreenDC()
2249 mdc = wx.MemoryDC()
2250 img = wx.EmptyBitmap(rect.width, rect.height)
2251 mdc.SelectObject(img)
2252 mdc.Blit (
2253 0, 0,
2254 rect.width, rect.height,
2255 wdc,
2256 rect.x, rect.y
2257 )
2258
2259
2260 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2261 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2262 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2263
2265
2266 raise ValueError('raised ValueError to test exception handling')
2267
2269 import wx.lib.inspection
2270 wx.lib.inspection.InspectionTool().Show()
2271
2273 webbrowser.open (
2274 url = 'https://bugs.launchpad.net/gnumed/',
2275 new = False,
2276 autoraise = True
2277 )
2278
2280 webbrowser.open (
2281 url = 'http://wiki.gnumed.de',
2282 new = False,
2283 autoraise = True
2284 )
2285
2287 webbrowser.open (
2288 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
2289 new = False,
2290 autoraise = True
2291 )
2292
2294 webbrowser.open (
2295 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
2296 new = False,
2297 autoraise = True
2298 )
2299
2306
2310
2313
2320
2325
2327 name = os.path.basename(gmLog2._logfile_name)
2328 name, ext = os.path.splitext(name)
2329 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2330 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2331
2332 dlg = wx.FileDialog (
2333 parent = self,
2334 message = _("Save current log as..."),
2335 defaultDir = new_path,
2336 defaultFile = new_name,
2337 wildcard = "%s (*.log)|*.log" % _("log files"),
2338 style = wx.SAVE
2339 )
2340 choice = dlg.ShowModal()
2341 new_name = dlg.GetPath()
2342 dlg.Destroy()
2343 if choice != wx.ID_OK:
2344 return True
2345
2346 _log.warning('syncing log file for backup to [%s]', new_name)
2347 gmLog2.flush()
2348 shutil.copy2(gmLog2._logfile_name, new_name)
2349 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2350
2353
2354
2355
2357 """This is the wx.EVT_CLOSE handler.
2358
2359 - framework still functional
2360 """
2361 _log.debug('gmTopLevelFrame.OnClose() start')
2362 self._clean_exit()
2363 self.Destroy()
2364 _log.debug('gmTopLevelFrame.OnClose() end')
2365 return True
2366
2372
2377
2385
2392
2399
2409
2417
2425
2433
2441
2450
2458
2475
2478
2481
2483
2484 pat = gmPerson.gmCurrentPatient()
2485 if not pat.connected:
2486 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2487 return False
2488
2489 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2490
2491 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2492 gmTools.mkdir(aDefDir)
2493
2494 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2495 dlg = wx.FileDialog (
2496 parent = self,
2497 message = _("Save patient's EMR journal as..."),
2498 defaultDir = aDefDir,
2499 defaultFile = fname,
2500 wildcard = aWildcard,
2501 style = wx.SAVE
2502 )
2503 choice = dlg.ShowModal()
2504 fname = dlg.GetPath()
2505 dlg.Destroy()
2506 if choice != wx.ID_OK:
2507 return True
2508
2509 _log.debug('exporting EMR journal to [%s]' % fname)
2510
2511 exporter = gmPatientExporter.cEMRJournalExporter()
2512
2513 wx.BeginBusyCursor()
2514 try:
2515 fname = exporter.export_to_file(filename = fname)
2516 except:
2517 wx.EndBusyCursor()
2518 gmGuiHelpers.gm_show_error (
2519 _('Error exporting patient EMR as chronological journal.'),
2520 _('EMR journal export')
2521 )
2522 raise
2523 wx.EndBusyCursor()
2524
2525 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2526
2527 return True
2528
2535
2537 curr_pat = gmPerson.gmCurrentPatient()
2538 if not curr_pat.connected:
2539 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2540 return
2541
2542 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2543 if tag is None:
2544 return
2545
2546 tag = curr_pat.add_tag(tag['pk_tag_image'])
2547 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2548 comment = wx.GetTextFromUser (
2549 message = msg,
2550 caption = _('Editing tag comment'),
2551 default_value = gmTools.coalesce(tag['comment'], u''),
2552 parent = self
2553 )
2554
2555 if comment == u'':
2556 return
2557
2558 if comment.strip() == tag['comment']:
2559 return
2560
2561 if comment == u' ':
2562 tag['comment'] = None
2563 else:
2564 tag['comment'] = comment.strip()
2565
2566 tag.save()
2567
2577
2579 curr_pat = gmPerson.gmCurrentPatient()
2580 if not curr_pat.connected:
2581 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2582 return False
2583
2584 enc = 'cp850'
2585 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2586 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2587 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2588
2591
2599
2607
2610
2617
2621
2624
2627
2630
2635
2637 """Cleanup helper.
2638
2639 - should ALWAYS be called when this program is
2640 to be terminated
2641 - ANY code that should be executed before a
2642 regular shutdown should go in here
2643 - framework still functional
2644 """
2645 _log.debug('gmTopLevelFrame._clean_exit() start')
2646
2647
2648 listener = gmBackendListener.gmBackendListener()
2649 try:
2650 listener.shutdown()
2651 except:
2652 _log.exception('cannot stop backend notifications listener thread')
2653
2654
2655 if _scripting_listener is not None:
2656 try:
2657 _scripting_listener.shutdown()
2658 except:
2659 _log.exception('cannot stop scripting listener thread')
2660
2661
2662 self.clock_update_timer.Stop()
2663 gmTimer.shutdown()
2664 gmPhraseWheel.shutdown()
2665
2666
2667 for call_back in self.__pre_exit_callbacks:
2668 try:
2669 call_back()
2670 except:
2671 print "*** pre-exit callback failed ***"
2672 print call_back
2673 _log.exception('callback [%s] failed', call_back)
2674
2675
2676 gmDispatcher.send(u'application_closing')
2677
2678
2679 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2680
2681
2682 curr_width, curr_height = self.GetClientSizeTuple()
2683 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2684 dbcfg = gmCfg.cCfgSQL()
2685 dbcfg.set (
2686 option = 'main.window.width',
2687 value = curr_width,
2688 workplace = gmSurgery.gmCurrentPractice().active_workplace
2689 )
2690 dbcfg.set (
2691 option = 'main.window.height',
2692 value = curr_height,
2693 workplace = gmSurgery.gmCurrentPractice().active_workplace
2694 )
2695
2696 if _cfg.get(option = 'debug'):
2697 print '---=== GNUmed shutdown ===---'
2698 try:
2699 print _('You have to manually close this window to finalize shutting down GNUmed.')
2700 print _('This is so that you can inspect the console output at your leisure.')
2701 except UnicodeEncodeError:
2702 print 'You have to manually close this window to finalize shutting down GNUmed.'
2703 print 'This is so that you can inspect the console output at your leisure.'
2704 print '---=== GNUmed shutdown ===---'
2705
2706
2707 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2708
2709
2710 import threading
2711 _log.debug("%s active threads", threading.activeCount())
2712 for t in threading.enumerate():
2713 _log.debug('thread %s', t)
2714
2715 _log.debug('gmTopLevelFrame._clean_exit() end')
2716
2717
2718
2720
2721 if _cfg.get(option = 'slave'):
2722 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2723 _cfg.get(option = 'slave personality'),
2724 _cfg.get(option = 'xml-rpc port')
2725 )
2726 else:
2727 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2728
2730 """Update title of main window based on template.
2731
2732 This gives nice tooltips on iconified GNUmed instances.
2733
2734 User research indicates that in the title bar people want
2735 the date of birth, not the age, so please stick to this
2736 convention.
2737 """
2738 args = {}
2739
2740 pat = gmPerson.gmCurrentPatient()
2741 if pat.connected:
2742 args['pat'] = u'%s %s %s (%s) #%d' % (
2743 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2744 pat['firstnames'],
2745 pat['lastnames'],
2746 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2747 pat['pk_identity']
2748 )
2749 else:
2750 args['pat'] = _('no patient')
2751
2752 args['prov'] = u'%s%s.%s' % (
2753 gmTools.coalesce(_provider['title'], u'', u'%s '),
2754 _provider['firstnames'][:1],
2755 _provider['lastnames']
2756 )
2757
2758 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2759
2760 self.SetTitle(self.__title_template % args)
2761
2762
2764 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2765 sb.SetStatusWidths([-1, 225])
2766
2767 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2768 self._cb_update_clock()
2769
2770 self.clock_update_timer.Start(milliseconds = 1000)
2771
2773 """Displays date and local time in the second slot of the status bar"""
2774 t = time.localtime(time.time())
2775 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2776 self.SetStatusText(st,1)
2777
2779 """Lock GNUmed client against unauthorized access"""
2780
2781
2782
2783 return
2784
2786 """Unlock the main notebook widgets
2787 As long as we are not logged into the database backend,
2788 all pages but the 'login' page of the main notebook widget
2789 are locked; i.e. not accessible by the user
2790 """
2791
2792
2793
2794
2795
2796 return
2797
2799 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2800
2802
2804
2805 self.__starting_up = True
2806
2807 gmExceptionHandlingWidgets.install_wx_exception_handler()
2808 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2809
2810
2811
2812
2813 self.SetAppName(u'gnumed')
2814 self.SetVendorName(u'The GNUmed Development Community.')
2815 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2816 paths.init_paths(wx = wx, app_name = u'gnumed')
2817
2818 if not self.__setup_prefs_file():
2819 return False
2820
2821 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2822
2823 self.__guibroker = gmGuiBroker.GuiBroker()
2824 self.__setup_platform()
2825
2826 if not self.__establish_backend_connection():
2827 return False
2828
2829 if not _cfg.get(option = 'skip-update-check'):
2830 self.__check_for_updates()
2831
2832 if _cfg.get(option = 'slave'):
2833 if not self.__setup_scripting_listener():
2834 return False
2835
2836
2837 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2838 frame.CentreOnScreen(wx.BOTH)
2839 self.SetTopWindow(frame)
2840 frame.Show(True)
2841
2842 if _cfg.get(option = 'debug'):
2843 self.RedirectStdio()
2844 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2845
2846
2847 print '---=== GNUmed startup ===---'
2848 print _('redirecting STDOUT/STDERR to this log window')
2849 print '---=== GNUmed startup ===---'
2850
2851 self.__setup_user_activity_timer()
2852 self.__register_events()
2853
2854 wx.CallAfter(self._do_after_init)
2855
2856 return True
2857
2859 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2860
2861 - after destroying all application windows and controls
2862 - before wx.Windows internal cleanup
2863 """
2864 _log.debug('gmApp.OnExit() start')
2865
2866 self.__shutdown_user_activity_timer()
2867
2868 if _cfg.get(option = 'debug'):
2869 self.RestoreStdio()
2870 sys.stdin = sys.__stdin__
2871 sys.stdout = sys.__stdout__
2872 sys.stderr = sys.__stderr__
2873
2874 _log.debug('gmApp.OnExit() end')
2875
2877 wx.Bell()
2878 wx.Bell()
2879 wx.Bell()
2880 _log.warning('unhandled event detected: QUERY_END_SESSION')
2881 _log.info('we should be saving ourselves from here')
2882 gmLog2.flush()
2883 print "unhandled event detected: QUERY_END_SESSION"
2884
2886 wx.Bell()
2887 wx.Bell()
2888 wx.Bell()
2889 _log.warning('unhandled event detected: END_SESSION')
2890 gmLog2.flush()
2891 print "unhandled event detected: END_SESSION"
2892
2903
2905 self.user_activity_detected = True
2906 evt.Skip()
2907
2909
2910 if self.user_activity_detected:
2911 self.elapsed_inactivity_slices = 0
2912 self.user_activity_detected = False
2913 self.elapsed_inactivity_slices += 1
2914 else:
2915 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2916
2917 pass
2918
2919 self.user_activity_timer.Start(oneShot = True)
2920
2921
2922
2924 try:
2925 kwargs['originated_in_database']
2926 print '==> got notification from database "%s":' % kwargs['signal']
2927 except KeyError:
2928 print '==> received signal from client: "%s"' % kwargs['signal']
2929
2930 del kwargs['signal']
2931 for key in kwargs.keys():
2932 print ' [%s]: %s' % (key, kwargs[key])
2933
2935 print "wx.lib.pubsub message:"
2936 print msg.topic
2937 print msg.data
2938
2944
2946 self.user_activity_detected = True
2947 self.elapsed_inactivity_slices = 0
2948
2949 self.max_user_inactivity_slices = 15
2950 self.user_activity_timer = gmTimer.cTimer (
2951 callback = self._on_user_activity_timer_expired,
2952 delay = 2000
2953 )
2954 self.user_activity_timer.Start(oneShot=True)
2955
2957 try:
2958 self.user_activity_timer.Stop()
2959 del self.user_activity_timer
2960 except:
2961 pass
2962
2964 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2965 wx.EVT_END_SESSION(self, self._on_end_session)
2966
2967
2968
2969
2970
2971 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2972
2973 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2974 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2975
2976 if _cfg.get(option = 'debug'):
2977 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
2978 _log.debug('connected old signal monitor')
2979 wx.lib.pubsub.Publisher().subscribe(listener = self._signal_debugging_monitor_pubsub)
2980 _log.debug('connected wx.lib.pubsub based signal monitor for all topics')
2981
2982
2983
2984
2985
2986
3002
3004 """Handle all the database related tasks necessary for startup."""
3005
3006
3007 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
3008
3009 from Gnumed.wxpython import gmAuthWidgets
3010 connected = gmAuthWidgets.connect_to_database (
3011 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
3012 require_version = not override
3013 )
3014 if not connected:
3015 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
3016 return False
3017
3018
3019 try:
3020 global _provider
3021 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
3022 except ValueError:
3023 account = gmPG2.get_current_user()
3024 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
3025 msg = _(
3026 'The database account [%s] cannot be used as a\n'
3027 'staff member login for GNUmed. There was an\n'
3028 'error retrieving staff details for it.\n\n'
3029 'Please ask your administrator for help.\n'
3030 ) % account
3031 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
3032 return False
3033
3034
3035 tmp = '%s%s %s (%s = %s)' % (
3036 gmTools.coalesce(_provider['title'], ''),
3037 _provider['firstnames'],
3038 _provider['lastnames'],
3039 _provider['short_alias'],
3040 _provider['db_user']
3041 )
3042 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
3043
3044
3045 surgery = gmSurgery.gmCurrentPractice()
3046 msg = surgery.db_logon_banner
3047 if msg.strip() != u'':
3048
3049 login = gmPG2.get_default_login()
3050 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
3051 login.database,
3052 gmTools.coalesce(login.host, u'localhost')
3053 ))
3054 msg = auth + msg + u'\n\n'
3055
3056 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3057 None,
3058
3059 -1,
3060 caption = _('Verifying database'),
3061 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3062 button_defs = [
3063 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3064 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3065 ]
3066 )
3067 go_on = dlg.ShowModal()
3068 dlg.Destroy()
3069 if go_on != wx.ID_YES:
3070 _log.info('user decided to not connect to this database')
3071 return False
3072
3073
3074 self.__check_db_lang()
3075
3076 return True
3077
3079 """Setup access to a config file for storing preferences."""
3080
3081 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3082
3083 candidates = []
3084 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3085 if explicit_file is not None:
3086 candidates.append(explicit_file)
3087
3088 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3089 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3090 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3091
3092 prefs_file = None
3093 for candidate in candidates:
3094 try:
3095 open(candidate, 'a+').close()
3096 prefs_file = candidate
3097 break
3098 except IOError:
3099 continue
3100
3101 if prefs_file is None:
3102 msg = _(
3103 'Cannot find configuration file in any of:\n'
3104 '\n'
3105 ' %s\n'
3106 'You may need to use the comand line option\n'
3107 '\n'
3108 ' --conf-file=<FILE>'
3109 ) % '\n '.join(candidates)
3110 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3111 return False
3112
3113 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3114 _log.info('user preferences file: %s', prefs_file)
3115
3116 return True
3117
3119
3120 from socket import error as SocketError
3121 from Gnumed.pycommon import gmScriptingListener
3122 from Gnumed.wxpython import gmMacro
3123
3124 slave_personality = gmTools.coalesce (
3125 _cfg.get (
3126 group = u'workplace',
3127 option = u'slave personality',
3128 source_order = [
3129 ('explicit', 'return'),
3130 ('workbase', 'return'),
3131 ('user', 'return'),
3132 ('system', 'return')
3133 ]
3134 ),
3135 u'gnumed-client'
3136 )
3137 _cfg.set_option(option = 'slave personality', value = slave_personality)
3138
3139
3140 port = int (
3141 gmTools.coalesce (
3142 _cfg.get (
3143 group = u'workplace',
3144 option = u'xml-rpc port',
3145 source_order = [
3146 ('explicit', 'return'),
3147 ('workbase', 'return'),
3148 ('user', 'return'),
3149 ('system', 'return')
3150 ]
3151 ),
3152 9999
3153 )
3154 )
3155 _cfg.set_option(option = 'xml-rpc port', value = port)
3156
3157 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3158 global _scripting_listener
3159 try:
3160 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3161 except SocketError, e:
3162 _log.exception('cannot start GNUmed XML-RPC server')
3163 gmGuiHelpers.gm_show_error (
3164 aMessage = (
3165 'Cannot start the GNUmed server:\n'
3166 '\n'
3167 ' [%s]'
3168 ) % e,
3169 aTitle = _('GNUmed startup')
3170 )
3171 return False
3172
3173 return True
3174
3195
3197 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3198 _log.warning("system locale is undefined (probably meaning 'C')")
3199 return True
3200
3201
3202 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3203 db_lang = rows[0]['lang']
3204
3205 if db_lang is None:
3206 _log.debug("database locale currently not set")
3207 msg = _(
3208 "There is no language selected in the database for user [%s].\n"
3209 "Your system language is currently set to [%s].\n\n"
3210 "Do you want to set the database language to '%s' ?\n\n"
3211 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3212 checkbox_msg = _('Remember to ignore missing language')
3213 else:
3214 _log.debug("current database locale: [%s]" % db_lang)
3215 msg = _(
3216 "The currently selected database language ('%s') does\n"
3217 "not match the current system language ('%s').\n"
3218 "\n"
3219 "Do you want to set the database language to '%s' ?\n"
3220 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3221 checkbox_msg = _('Remember to ignore language mismatch')
3222
3223
3224 if db_lang == gmI18N.system_locale_level['full']:
3225 _log.debug('Database locale (%s) up to date.' % db_lang)
3226 return True
3227 if db_lang == gmI18N.system_locale_level['country']:
3228 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3229 return True
3230 if db_lang == gmI18N.system_locale_level['language']:
3231 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3232 return True
3233
3234 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3235
3236
3237 ignored_sys_lang = _cfg.get (
3238 group = u'backend',
3239 option = u'ignored mismatching system locale',
3240 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3241 )
3242
3243
3244 if gmI18N.system_locale == ignored_sys_lang:
3245 _log.info('configured to ignore system-to-database locale mismatch')
3246 return True
3247
3248
3249 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3250 None,
3251 -1,
3252 caption = _('Checking database language settings'),
3253 question = msg,
3254 button_defs = [
3255 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3256 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3257 ],
3258 show_checkbox = True,
3259 checkbox_msg = checkbox_msg,
3260 checkbox_tooltip = _(
3261 'Checking this will make GNUmed remember your decision\n'
3262 'until the system language is changed.\n'
3263 '\n'
3264 'You can also reactivate this inquiry by removing the\n'
3265 'corresponding "ignore" option from the configuration file\n'
3266 '\n'
3267 ' [%s]'
3268 ) % _cfg.get(option = 'user_preferences_file')
3269 )
3270 decision = dlg.ShowModal()
3271 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3272 dlg.Destroy()
3273
3274 if decision == wx.ID_NO:
3275 if not remember_ignoring_problem:
3276 return True
3277 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3278 gmCfg2.set_option_in_INI_file (
3279 filename = _cfg.get(option = 'user_preferences_file'),
3280 group = 'backend',
3281 option = 'ignored mismatching system locale',
3282 value = gmI18N.system_locale
3283 )
3284 return True
3285
3286
3287 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3288 if len(lang) > 0:
3289
3290
3291 rows, idx = gmPG2.run_rw_queries (
3292 link_obj = None,
3293 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3294 return_data = True
3295 )
3296 if rows[0][0]:
3297 _log.debug("Successfully set database language to [%s]." % lang)
3298 else:
3299 _log.error('Cannot set database language to [%s].' % lang)
3300 continue
3301 return True
3302
3303
3304 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3305 gmPG2.run_rw_queries(queries = [{
3306 'cmd': u'select i18n.force_curr_lang(%s)',
3307 'args': [gmI18N.system_locale_level['country']]
3308 }])
3309
3310 return True
3311
3313 try:
3314 kwargs['originated_in_database']
3315 print '==> got notification from database "%s":' % kwargs['signal']
3316 except KeyError:
3317 print '==> received signal from client: "%s"' % kwargs['signal']
3318
3319 del kwargs['signal']
3320 for key in kwargs.keys():
3321
3322 try: print ' [%s]: %s' % (key, kwargs[key])
3323 except: print 'cannot print signal information'
3324
3326
3327 try:
3328 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3329 print ' data: %s' % msg.data
3330 print msg
3331 except: print 'problem printing pubsub message information'
3332
3334
3335 if _cfg.get(option = 'debug'):
3336 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3337 _log.debug('gmDispatcher signal monitor activated')
3338 wx.lib.pubsub.Publisher().subscribe (
3339 listener = _signal_debugging_monitor_pubsub
3340
3341 )
3342 _log.debug('wx.lib.pubsub signal monitor activated')
3343
3344 wx.InitAllImageHandlers()
3345
3346
3347
3348 app = gmApp(redirect = False, clearSigInt = False)
3349 app.MainLoop()
3350
3351
3352
3353 if __name__ == '__main__':
3354
3355 from GNUmed.pycommon import gmI18N
3356 gmI18N.activate_locale()
3357 gmI18N.install_domain()
3358
3359 _log.info('Starting up as main module.')
3360 main()
3361
3362
3363