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
1040
1041
1042
1045
1053
1054
1055
1070
1093
1095 from Gnumed.wxpython import gmAbout
1096 contribs = gmAbout.cContributorsDlg (
1097 parent = self,
1098 id = -1,
1099 title = _('GNUmed contributors'),
1100 size = wx.Size(400,600),
1101 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1102 )
1103 contribs.ShowModal()
1104 del contribs
1105 del gmAbout
1106
1107
1108
1110 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1111 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1112 self.Close(True)
1113 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1114
1117
1119 send = gmGuiHelpers.gm_show_question (
1120 _('This will send a notification about database downtime\n'
1121 'to all GNUmed clients connected to your database.\n'
1122 '\n'
1123 'Do you want to send the notification ?\n'
1124 ),
1125 _('Announcing database maintenance downtime')
1126 )
1127 if not send:
1128 return
1129 gmPG2.send_maintenance_notification()
1130
1131
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
1164
1165
1166
1167
1168
1181
1182 gmCfgWidgets.configure_string_option (
1183 message = _(
1184 'Some network installations cannot cope with loading\n'
1185 'documents of arbitrary size in one piece from the\n'
1186 'database (mainly observed on older Windows versions)\n.'
1187 '\n'
1188 'Under such circumstances documents need to be retrieved\n'
1189 'in chunks and reassembled on the client.\n'
1190 '\n'
1191 'Here you can set the size (in Bytes) above which\n'
1192 'GNUmed will retrieve documents in chunks. Setting this\n'
1193 'value to 0 will disable the chunking protocol.'
1194 ),
1195 option = 'horstspace.blob_export_chunk_size',
1196 bias = 'workplace',
1197 default_value = 1024 * 1024,
1198 validator = is_valid
1199 )
1200
1201
1202
1270
1274
1275
1276
1285
1286 gmCfgWidgets.configure_string_option (
1287 message = _(
1288 'When GNUmed cannot find an OpenOffice server it\n'
1289 'will try to start one. OpenOffice, however, needs\n'
1290 'some time to fully start up.\n'
1291 '\n'
1292 'Here you can set the time for GNUmed to wait for OOo.\n'
1293 ),
1294 option = 'external.ooo.startup_settle_time',
1295 bias = 'workplace',
1296 default_value = 2.0,
1297 validator = is_valid
1298 )
1299
1302
1317
1318 gmCfgWidgets.configure_string_option (
1319 message = _(
1320 'GNUmed will use this URL to access a website which lets\n'
1321 'you report an adverse drug reaction (ADR).\n'
1322 '\n'
1323 'If you leave this empty it will fall back\n'
1324 'to an URL for reporting ADRs in Germany.'
1325 ),
1326 option = 'external.urls.report_ADR',
1327 bias = 'user',
1328 default_value = german_default,
1329 validator = is_valid
1330 )
1331
1345
1346 gmCfgWidgets.configure_string_option (
1347 message = _(
1348 'GNUmed will use this URL to access a website which lets\n'
1349 'you report an adverse vaccination reaction (vADR).\n'
1350 '\n'
1351 'If you set it to a specific address that URL must be\n'
1352 'accessible now. If you leave it empty it will fall back\n'
1353 'to the URL for reporting other adverse drug reactions.'
1354 ),
1355 option = 'external.urls.report_vaccine_ADR',
1356 bias = 'user',
1357 default_value = german_default,
1358 validator = is_valid
1359 )
1360
1374
1375 gmCfgWidgets.configure_string_option (
1376 message = _(
1377 'GNUmed will use this URL to access an encyclopedia of\n'
1378 'measurement/lab methods from within the measurments grid.\n'
1379 '\n'
1380 'You can leave this empty but to set it to a specific\n'
1381 'address the URL must be accessible now.'
1382 ),
1383 option = 'external.urls.measurements_encyclopedia',
1384 bias = 'user',
1385 default_value = german_default,
1386 validator = is_valid
1387 )
1388
1402
1403 gmCfgWidgets.configure_string_option (
1404 message = _(
1405 'GNUmed will use this URL to access a page showing\n'
1406 'vaccination schedules.\n'
1407 '\n'
1408 'You can leave this empty but to set it to a specific\n'
1409 'address the URL must be accessible now.'
1410 ),
1411 option = 'external.urls.vaccination_plans',
1412 bias = 'user',
1413 default_value = german_default,
1414 validator = is_valid
1415 )
1416
1429
1430 gmCfgWidgets.configure_string_option (
1431 message = _(
1432 'Enter the shell command with which to start the\n'
1433 'the ACS risk assessment calculator.\n'
1434 '\n'
1435 'GNUmed will try to verify the path which may,\n'
1436 'however, fail if you are using an emulator such\n'
1437 'as Wine. Nevertheless, starting the calculator\n'
1438 'will work as long as the shell command is correct\n'
1439 'despite the failing test.'
1440 ),
1441 option = 'external.tools.acs_risk_calculator_cmd',
1442 bias = 'user',
1443 validator = is_valid
1444 )
1445
1448
1461
1462 gmCfgWidgets.configure_string_option (
1463 message = _(
1464 'Enter the shell command with which to start\n'
1465 'the FreeDiams drug database frontend.\n'
1466 '\n'
1467 'GNUmed will try to verify that path.'
1468 ),
1469 option = 'external.tools.freediams_cmd',
1470 bias = 'workplace',
1471 default_value = None,
1472 validator = is_valid
1473 )
1474
1487
1488 gmCfgWidgets.configure_string_option (
1489 message = _(
1490 'Enter the shell command with which to start the\n'
1491 'the IFAP drug database.\n'
1492 '\n'
1493 'GNUmed will try to verify the path which may,\n'
1494 'however, fail if you are using an emulator such\n'
1495 'as Wine. Nevertheless, starting IFAP will work\n'
1496 'as long as the shell command is correct despite\n'
1497 'the failing test.'
1498 ),
1499 option = 'external.ifap-win.shell_command',
1500 bias = 'workplace',
1501 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1502 validator = is_valid
1503 )
1504
1505
1506
1555
1556
1557
1574
1577
1580
1585
1586 gmCfgWidgets.configure_string_option (
1587 message = _(
1588 'When a patient is activated GNUmed checks the\n'
1589 "proximity of the patient's birthday.\n"
1590 '\n'
1591 'If the birthday falls within the range of\n'
1592 ' "today %s <the interval you set here>"\n'
1593 'GNUmed will remind you of the recent or\n'
1594 'imminent anniversary.'
1595 ) % u'\u2213',
1596 option = u'patient_search.dob_warn_interval',
1597 bias = 'user',
1598 default_value = '1 week',
1599 validator = is_valid
1600 )
1601
1603
1604 gmCfgWidgets.configure_boolean_option (
1605 parent = self,
1606 question = _(
1607 'When adding progress notes do you want to\n'
1608 'allow opening several unassociated, new\n'
1609 'episodes for a patient at once ?\n'
1610 '\n'
1611 'This can be particularly helpful when entering\n'
1612 'progress notes on entirely new patients presenting\n'
1613 'with a multitude of problems on their first visit.'
1614 ),
1615 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1616 button_tooltips = [
1617 _('Yes, allow for multiple new episodes concurrently.'),
1618 _('No, only allow editing one new episode at a time.')
1619 ]
1620 )
1621
1623
1624 gmCfgWidgets.configure_boolean_option (
1625 parent = self,
1626 question = _(
1627 'When activating a patient, do you want GNUmed to\n'
1628 'auto-open editors for all active problems that were\n'
1629 'touched upon during the current and the most recent\n'
1630 'encounter ?'
1631 ),
1632 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1633 button_tooltips = [
1634 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1635 _('No, only auto-open one editor for a new, unassociated problem.')
1636 ]
1637 )
1638
1684
1685
1686
1689
1692
1706
1708 gmCfgWidgets.configure_boolean_option (
1709 parent = self,
1710 question = _(
1711 'Do you want GNUmed to show the encounter\n'
1712 'details editor when changing the active patient ?'
1713 ),
1714 option = 'encounter.show_editor_before_patient_change',
1715 button_tooltips = [
1716 _('Yes, show the encounter editor if it seems appropriate.'),
1717 _('No, never show the encounter editor even if it would seem useful.')
1718 ]
1719 )
1720
1725
1726 gmCfgWidgets.configure_string_option (
1727 message = _(
1728 'When a patient is activated GNUmed checks the\n'
1729 'chart for encounters lacking any entries.\n'
1730 '\n'
1731 'Any such encounters older than what you set\n'
1732 'here will be removed from the medical record.\n'
1733 '\n'
1734 'To effectively disable removal of such encounters\n'
1735 'set this option to an improbable value.\n'
1736 ),
1737 option = 'encounter.ttl_if_empty',
1738 bias = 'user',
1739 default_value = '1 week',
1740 validator = is_valid
1741 )
1742
1747
1748 gmCfgWidgets.configure_string_option (
1749 message = _(
1750 'When a patient is activated GNUmed checks the\n'
1751 'age of the most recent encounter.\n'
1752 '\n'
1753 'If that encounter is younger than this age\n'
1754 'the existing encounter will be continued.\n'
1755 '\n'
1756 '(If it is really old a new encounter is\n'
1757 ' started, or else GNUmed will ask you.)\n'
1758 ),
1759 option = 'encounter.minimum_ttl',
1760 bias = 'user',
1761 default_value = '1 hour 30 minutes',
1762 validator = is_valid
1763 )
1764
1769
1770 gmCfgWidgets.configure_string_option (
1771 message = _(
1772 'When a patient is activated GNUmed checks the\n'
1773 'age of the most recent encounter.\n'
1774 '\n'
1775 'If that encounter is older than this age\n'
1776 'GNUmed will always start a new encounter.\n'
1777 '\n'
1778 '(If it is very recent the existing encounter\n'
1779 ' is continued, or else GNUmed will ask you.)\n'
1780 ),
1781 option = 'encounter.maximum_ttl',
1782 bias = 'user',
1783 default_value = '6 hours',
1784 validator = is_valid
1785 )
1786
1795
1796 gmCfgWidgets.configure_string_option (
1797 message = _(
1798 'At any time there can only be one open (ongoing)\n'
1799 'episode for each health issue.\n'
1800 '\n'
1801 'When you try to open (add data to) an episode on a health\n'
1802 'issue GNUmed will check for an existing open episode on\n'
1803 'that issue. If there is any it will check the age of that\n'
1804 'episode. The episode is closed if it has been dormant (no\n'
1805 'data added, that is) for the period of time (in days) you\n'
1806 'set here.\n'
1807 '\n'
1808 "If the existing episode hasn't been dormant long enough\n"
1809 'GNUmed will consult you what to do.\n'
1810 '\n'
1811 'Enter maximum episode dormancy in DAYS:'
1812 ),
1813 option = 'episode.ttl',
1814 bias = 'user',
1815 default_value = 60,
1816 validator = is_valid
1817 )
1818
1849
1864
1889
1901
1902 gmCfgWidgets.configure_string_option (
1903 message = _(
1904 'GNUmed can check for new releases being available. To do\n'
1905 'so it needs to load version information from an URL.\n'
1906 '\n'
1907 'The default URL is:\n'
1908 '\n'
1909 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1910 '\n'
1911 'but you can configure any other URL locally. Note\n'
1912 'that you must enter the location as a valid URL.\n'
1913 'Depending on the URL the client will need online\n'
1914 'access when checking for updates.'
1915 ),
1916 option = u'horstspace.update.url',
1917 bias = u'workplace',
1918 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1919 validator = is_valid
1920 )
1921
1939
1956
1967
1968 gmCfgWidgets.configure_string_option (
1969 message = _(
1970 'GNUmed can show the document review dialog after\n'
1971 'calling the appropriate viewer for that document.\n'
1972 '\n'
1973 'Select the conditions under which you want\n'
1974 'GNUmed to do so:\n'
1975 '\n'
1976 ' 0: never display the review dialog\n'
1977 ' 1: always display the dialog\n'
1978 ' 2: only if there is no previous review by me\n'
1979 '\n'
1980 'Note that if a viewer is configured to not block\n'
1981 'GNUmed during document display the review dialog\n'
1982 'will actually appear in parallel to the viewer.'
1983 ),
1984 option = u'horstspace.document_viewer.review_after_display',
1985 bias = u'user',
1986 default_value = 2,
1987 validator = is_valid
1988 )
1989
1991
1992
1993 master_data_lists = [
1994 'adr',
1995 'drugs',
1996 'codes',
1997 'substances_in_brands',
1998 'substances',
1999 'labs',
2000 'form_templates',
2001 'doc_types',
2002 'enc_types',
2003 'text_expansions',
2004 'meta_test_types',
2005
2006 'patient_tags',
2007 'provinces',
2008 'db_translations',
2009 'test_types',
2010 'org_units',
2011 'vacc_indications',
2012 'vaccines',
2013 'workplaces'
2014 ]
2015
2016 master_data_list_names = {
2017 'adr': _('Addresses (likely slow)'),
2018 'drugs': _('Branded drugs (as marketed)'),
2019 'codes': _('Codes and their respective terms'),
2020 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2021 'labs': _('Diagnostic organizations (path labs, ...)'),
2022 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2023 'doc_types': _('Document types'),
2024 'enc_types': _('Encounter types'),
2025 'text_expansions': _('Keyword based text expansion macros'),
2026 'meta_test_types': _('Meta test/measurement types'),
2027
2028 'patient_tags': _('Patient tags'),
2029 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2030 'db_translations': _('String translations in the database'),
2031 'test_types': _('Test/measurement types'),
2032 'org_units': _('Units of organizations (branches, sites, departments, parts, ...'),
2033 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2034 'vaccines': _('Vaccines'),
2035 'workplaces': _('Workplace profiles (which plugins to load)'),
2036 'substances': _('Consumable substances')
2037 }
2038
2039 map_list2handler = {
2040 'org_units': gmOrganizationWidgets.manage_org_units,
2041 'form_templates': gmFormWidgets.manage_form_templates,
2042 'doc_types': gmDocumentWidgets.manage_document_types,
2043 'text_expansions': gmProviderInboxWidgets.configure_keyword_text_expansion,
2044 'db_translations': gmI18nWidgets.manage_translations,
2045 'codes': gmCodingWidgets.browse_coded_terms,
2046 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2047 'provinces': gmPersonContactWidgets.manage_provinces,
2048 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins,
2049 'drugs': gmMedicationWidgets.manage_branded_drugs,
2050 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2051 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2052 'test_types': gmMeasurementWidgets.manage_measurement_types,
2053 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2054 'vaccines': gmVaccWidgets.manage_vaccines,
2055 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2056
2057 'adr': gmPersonContactWidgets.manage_addresses,
2058 'substances': gmMedicationWidgets.manage_consumable_substances,
2059 'patient_tags': gmDemographicsWidgets.manage_tag_images
2060 }
2061
2062
2063 def edit(item):
2064 try: map_list2handler[item](parent = self)
2065 except KeyError: pass
2066 return False
2067
2068
2069 gmListWidgets.get_choices_from_list (
2070 parent = self,
2071 caption = _('Master data management'),
2072 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2073 data = master_data_lists,
2074 columns = [_('Select the list you want to manage:')],
2075 edit_callback = edit,
2076 single_selection = True,
2077 ignore_OK_button = True
2078 )
2079
2093
2095
2096 curr_pat = gmPerson.gmCurrentPatient()
2097
2098 arriba = gmArriba.cArriba()
2099 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2100 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2101 return
2102
2103
2104 if curr_pat is None:
2105 return
2106
2107 if arriba.pdf_result is None:
2108 return
2109
2110 doc = gmDocumentWidgets.save_file_as_new_document (
2111 parent = self,
2112 filename = arriba.pdf_result,
2113 document_type = _('risk assessment')
2114 )
2115
2116 try: os.remove(arriba.pdf_result)
2117 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2118
2119 if doc is None:
2120 return
2121
2122 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2123 doc.save()
2124
2125 try:
2126 open(arriba.xml_result).close()
2127 part = doc.add_part(file = arriba.xml_result)
2128 except StandardError:
2129 _log.exception('error accessing [%s]', arriba.xml_result)
2130 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2131
2132 if part is None:
2133 return
2134
2135 part['obj_comment'] = u'XML-Daten'
2136 part['filename'] = u'arriba-result.xml'
2137 part.save()
2138
2140
2141 dbcfg = gmCfg.cCfgSQL()
2142 cmd = dbcfg.get2 (
2143 option = u'external.tools.acs_risk_calculator_cmd',
2144 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2145 bias = 'user'
2146 )
2147
2148 if cmd is None:
2149 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2150 return
2151
2152 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
2153 try:
2154 subprocess.check_call (
2155 args = (cmd,),
2156 close_fds = True,
2157 cwd = cwd
2158 )
2159 except (OSError, ValueError, subprocess.CalledProcessError):
2160 _log.exception('there was a problem executing [%s]', cmd)
2161 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2162 return
2163
2164 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2165 for pdf in pdfs:
2166 try:
2167 open(pdf).close()
2168 except:
2169 _log.exception('error accessing [%s]', pdf)
2170 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2171 continue
2172
2173 doc = gmDocumentWidgets.save_file_as_new_document (
2174 parent = self,
2175 filename = pdf,
2176 document_type = u'risk assessment'
2177 )
2178
2179 try:
2180 os.remove(pdf)
2181 except StandardError:
2182 _log.exception('cannot remove [%s]', pdf)
2183
2184 if doc is None:
2185 continue
2186 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2187 doc.save()
2188
2189 return
2190
2192 dlg = gmSnellen.cSnellenCfgDlg()
2193 if dlg.ShowModal() != wx.ID_OK:
2194 return
2195
2196 frame = gmSnellen.cSnellenChart (
2197 width = dlg.vals[0],
2198 height = dlg.vals[1],
2199 alpha = dlg.vals[2],
2200 mirr = dlg.vals[3],
2201 parent = None
2202 )
2203 frame.CentreOnScreen(wx.BOTH)
2204
2205
2206 frame.Show(True)
2207
2208
2210 webbrowser.open (
2211 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
2212 new = False,
2213 autoraise = True
2214 )
2215
2218
2220 webbrowser.open (
2221 url = 'http://www.kompendium.ch',
2222 new = False,
2223 autoraise = True
2224 )
2225
2226
2227
2231
2232
2233
2235 wx.CallAfter(self.__save_screenshot)
2236 evt.Skip()
2237
2239
2240 time.sleep(0.5)
2241
2242 rect = self.GetRect()
2243
2244
2245 if sys.platform == 'linux2':
2246 client_x, client_y = self.ClientToScreen((0, 0))
2247 border_width = client_x - rect.x
2248 title_bar_height = client_y - rect.y
2249
2250 if self.GetMenuBar():
2251 title_bar_height /= 2
2252 rect.width += (border_width * 2)
2253 rect.height += title_bar_height + border_width
2254
2255 wdc = wx.ScreenDC()
2256 mdc = wx.MemoryDC()
2257 img = wx.EmptyBitmap(rect.width, rect.height)
2258 mdc.SelectObject(img)
2259 mdc.Blit (
2260 0, 0,
2261 rect.width, rect.height,
2262 wdc,
2263 rect.x, rect.y
2264 )
2265
2266
2267 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2268 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2269 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2270
2272
2273 raise ValueError('raised ValueError to test exception handling')
2274
2276 import wx.lib.inspection
2277 wx.lib.inspection.InspectionTool().Show()
2278
2280 webbrowser.open (
2281 url = 'https://bugs.launchpad.net/gnumed/',
2282 new = False,
2283 autoraise = True
2284 )
2285
2287 webbrowser.open (
2288 url = 'http://wiki.gnumed.de',
2289 new = False,
2290 autoraise = True
2291 )
2292
2294 webbrowser.open (
2295 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
2296 new = False,
2297 autoraise = True
2298 )
2299
2301 webbrowser.open (
2302 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
2303 new = False,
2304 autoraise = True
2305 )
2306
2313
2317
2320
2327
2332
2334 name = os.path.basename(gmLog2._logfile_name)
2335 name, ext = os.path.splitext(name)
2336 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2337 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2338
2339 dlg = wx.FileDialog (
2340 parent = self,
2341 message = _("Save current log as..."),
2342 defaultDir = new_path,
2343 defaultFile = new_name,
2344 wildcard = "%s (*.log)|*.log" % _("log files"),
2345 style = wx.SAVE
2346 )
2347 choice = dlg.ShowModal()
2348 new_name = dlg.GetPath()
2349 dlg.Destroy()
2350 if choice != wx.ID_OK:
2351 return True
2352
2353 _log.warning('syncing log file for backup to [%s]', new_name)
2354 gmLog2.flush()
2355 shutil.copy2(gmLog2._logfile_name, new_name)
2356 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2357
2360
2361
2362
2364 """This is the wx.EVT_CLOSE handler.
2365
2366 - framework still functional
2367 """
2368 _log.debug('gmTopLevelFrame.OnClose() start')
2369 self._clean_exit()
2370 self.Destroy()
2371 _log.debug('gmTopLevelFrame.OnClose() end')
2372 return True
2373
2379
2384
2392
2399
2406
2416
2424
2432
2440
2448
2457
2465
2482
2485
2488
2490
2491 pat = gmPerson.gmCurrentPatient()
2492 if not pat.connected:
2493 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2494 return False
2495
2496 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2497
2498 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2499 gmTools.mkdir(aDefDir)
2500
2501 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2502 dlg = wx.FileDialog (
2503 parent = self,
2504 message = _("Save patient's EMR journal as..."),
2505 defaultDir = aDefDir,
2506 defaultFile = fname,
2507 wildcard = aWildcard,
2508 style = wx.SAVE
2509 )
2510 choice = dlg.ShowModal()
2511 fname = dlg.GetPath()
2512 dlg.Destroy()
2513 if choice != wx.ID_OK:
2514 return True
2515
2516 _log.debug('exporting EMR journal to [%s]' % fname)
2517
2518 exporter = gmPatientExporter.cEMRJournalExporter()
2519
2520 wx.BeginBusyCursor()
2521 try:
2522 fname = exporter.export_to_file(filename = fname)
2523 except:
2524 wx.EndBusyCursor()
2525 gmGuiHelpers.gm_show_error (
2526 _('Error exporting patient EMR as chronological journal.'),
2527 _('EMR journal export')
2528 )
2529 raise
2530 wx.EndBusyCursor()
2531
2532 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2533
2534 return True
2535
2542
2544 curr_pat = gmPerson.gmCurrentPatient()
2545 if not curr_pat.connected:
2546 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2547 return
2548
2549 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2550 if tag is None:
2551 return
2552
2553 tag = curr_pat.add_tag(tag['pk_tag_image'])
2554 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2555 comment = wx.GetTextFromUser (
2556 message = msg,
2557 caption = _('Editing tag comment'),
2558 default_value = gmTools.coalesce(tag['comment'], u''),
2559 parent = self
2560 )
2561
2562 if comment == u'':
2563 return
2564
2565 if comment.strip() == tag['comment']:
2566 return
2567
2568 if comment == u' ':
2569 tag['comment'] = None
2570 else:
2571 tag['comment'] = comment.strip()
2572
2573 tag.save()
2574
2584
2586 curr_pat = gmPerson.gmCurrentPatient()
2587 if not curr_pat.connected:
2588 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2589 return False
2590
2591 enc = 'cp850'
2592 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2593 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2594 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2595
2598
2606
2614
2617
2624
2628
2631
2634
2637
2642
2644 """Cleanup helper.
2645
2646 - should ALWAYS be called when this program is
2647 to be terminated
2648 - ANY code that should be executed before a
2649 regular shutdown should go in here
2650 - framework still functional
2651 """
2652 _log.debug('gmTopLevelFrame._clean_exit() start')
2653
2654
2655 listener = gmBackendListener.gmBackendListener()
2656 try:
2657 listener.shutdown()
2658 except:
2659 _log.exception('cannot stop backend notifications listener thread')
2660
2661
2662 if _scripting_listener is not None:
2663 try:
2664 _scripting_listener.shutdown()
2665 except:
2666 _log.exception('cannot stop scripting listener thread')
2667
2668
2669 self.clock_update_timer.Stop()
2670 gmTimer.shutdown()
2671 gmPhraseWheel.shutdown()
2672
2673
2674 for call_back in self.__pre_exit_callbacks:
2675 try:
2676 call_back()
2677 except:
2678 print "*** pre-exit callback failed ***"
2679 print call_back
2680 _log.exception('callback [%s] failed', call_back)
2681
2682
2683 gmDispatcher.send(u'application_closing')
2684
2685
2686 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2687
2688
2689 curr_width, curr_height = self.GetClientSizeTuple()
2690 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2691 dbcfg = gmCfg.cCfgSQL()
2692 dbcfg.set (
2693 option = 'main.window.width',
2694 value = curr_width,
2695 workplace = gmSurgery.gmCurrentPractice().active_workplace
2696 )
2697 dbcfg.set (
2698 option = 'main.window.height',
2699 value = curr_height,
2700 workplace = gmSurgery.gmCurrentPractice().active_workplace
2701 )
2702
2703 if _cfg.get(option = 'debug'):
2704 print '---=== GNUmed shutdown ===---'
2705 try:
2706 print _('You have to manually close this window to finalize shutting down GNUmed.')
2707 print _('This is so that you can inspect the console output at your leisure.')
2708 except UnicodeEncodeError:
2709 print 'You have to manually close this window to finalize shutting down GNUmed.'
2710 print 'This is so that you can inspect the console output at your leisure.'
2711 print '---=== GNUmed shutdown ===---'
2712
2713
2714 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2715
2716
2717 import threading
2718 _log.debug("%s active threads", threading.activeCount())
2719 for t in threading.enumerate():
2720 _log.debug('thread %s', t)
2721
2722 _log.debug('gmTopLevelFrame._clean_exit() end')
2723
2724
2725
2727
2728 if _cfg.get(option = 'slave'):
2729 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2730 _cfg.get(option = 'slave personality'),
2731 _cfg.get(option = 'xml-rpc port')
2732 )
2733 else:
2734 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2735
2737 """Update title of main window based on template.
2738
2739 This gives nice tooltips on iconified GNUmed instances.
2740
2741 User research indicates that in the title bar people want
2742 the date of birth, not the age, so please stick to this
2743 convention.
2744 """
2745 args = {}
2746
2747 pat = gmPerson.gmCurrentPatient()
2748 if pat.connected:
2749 args['pat'] = u'%s %s %s (%s) #%d' % (
2750 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2751 pat['firstnames'],
2752 pat['lastnames'],
2753 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2754 pat['pk_identity']
2755 )
2756 else:
2757 args['pat'] = _('no patient')
2758
2759 args['prov'] = u'%s%s.%s' % (
2760 gmTools.coalesce(_provider['title'], u'', u'%s '),
2761 _provider['firstnames'][:1],
2762 _provider['lastnames']
2763 )
2764
2765 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2766
2767 self.SetTitle(self.__title_template % args)
2768
2769
2771 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2772 sb.SetStatusWidths([-1, 225])
2773
2774 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2775 self._cb_update_clock()
2776
2777 self.clock_update_timer.Start(milliseconds = 1000)
2778
2780 """Displays date and local time in the second slot of the status bar"""
2781 t = time.localtime(time.time())
2782 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2783 self.SetStatusText(st,1)
2784
2786 """Lock GNUmed client against unauthorized access"""
2787
2788
2789
2790 return
2791
2793 """Unlock the main notebook widgets
2794 As long as we are not logged into the database backend,
2795 all pages but the 'login' page of the main notebook widget
2796 are locked; i.e. not accessible by the user
2797 """
2798
2799
2800
2801
2802
2803 return
2804
2806 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2807
2809
2811
2812 self.__starting_up = True
2813
2814 gmExceptionHandlingWidgets.install_wx_exception_handler()
2815 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2816
2817
2818
2819
2820 self.SetAppName(u'gnumed')
2821 self.SetVendorName(u'The GNUmed Development Community.')
2822 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2823 paths.init_paths(wx = wx, app_name = u'gnumed')
2824
2825 if not self.__setup_prefs_file():
2826 return False
2827
2828 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2829
2830 self.__guibroker = gmGuiBroker.GuiBroker()
2831 self.__setup_platform()
2832
2833 if not self.__establish_backend_connection():
2834 return False
2835
2836 if not _cfg.get(option = 'skip-update-check'):
2837 self.__check_for_updates()
2838
2839 if _cfg.get(option = 'slave'):
2840 if not self.__setup_scripting_listener():
2841 return False
2842
2843
2844 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2845 frame.CentreOnScreen(wx.BOTH)
2846 self.SetTopWindow(frame)
2847 frame.Show(True)
2848
2849 if _cfg.get(option = 'debug'):
2850 self.RedirectStdio()
2851 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2852
2853
2854 print '---=== GNUmed startup ===---'
2855 print _('redirecting STDOUT/STDERR to this log window')
2856 print '---=== GNUmed startup ===---'
2857
2858 self.__setup_user_activity_timer()
2859 self.__register_events()
2860
2861 wx.CallAfter(self._do_after_init)
2862
2863 return True
2864
2866 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2867
2868 - after destroying all application windows and controls
2869 - before wx.Windows internal cleanup
2870 """
2871 _log.debug('gmApp.OnExit() start')
2872
2873 self.__shutdown_user_activity_timer()
2874
2875 if _cfg.get(option = 'debug'):
2876 self.RestoreStdio()
2877 sys.stdin = sys.__stdin__
2878 sys.stdout = sys.__stdout__
2879 sys.stderr = sys.__stderr__
2880
2881 _log.debug('gmApp.OnExit() end')
2882
2884 wx.Bell()
2885 wx.Bell()
2886 wx.Bell()
2887 _log.warning('unhandled event detected: QUERY_END_SESSION')
2888 _log.info('we should be saving ourselves from here')
2889 gmLog2.flush()
2890 print "unhandled event detected: QUERY_END_SESSION"
2891
2893 wx.Bell()
2894 wx.Bell()
2895 wx.Bell()
2896 _log.warning('unhandled event detected: END_SESSION')
2897 gmLog2.flush()
2898 print "unhandled event detected: END_SESSION"
2899
2910
2912 self.user_activity_detected = True
2913 evt.Skip()
2914
2916
2917 if self.user_activity_detected:
2918 self.elapsed_inactivity_slices = 0
2919 self.user_activity_detected = False
2920 self.elapsed_inactivity_slices += 1
2921 else:
2922 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2923
2924 pass
2925
2926 self.user_activity_timer.Start(oneShot = True)
2927
2928
2929
2931 try:
2932 kwargs['originated_in_database']
2933 print '==> got notification from database "%s":' % kwargs['signal']
2934 except KeyError:
2935 print '==> received signal from client: "%s"' % kwargs['signal']
2936
2937 del kwargs['signal']
2938 for key in kwargs.keys():
2939 print ' [%s]: %s' % (key, kwargs[key])
2940
2942 print "wx.lib.pubsub message:"
2943 print msg.topic
2944 print msg.data
2945
2951
2953 self.user_activity_detected = True
2954 self.elapsed_inactivity_slices = 0
2955
2956 self.max_user_inactivity_slices = 15
2957 self.user_activity_timer = gmTimer.cTimer (
2958 callback = self._on_user_activity_timer_expired,
2959 delay = 2000
2960 )
2961 self.user_activity_timer.Start(oneShot=True)
2962
2964 try:
2965 self.user_activity_timer.Stop()
2966 del self.user_activity_timer
2967 except:
2968 pass
2969
2971 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2972 wx.EVT_END_SESSION(self, self._on_end_session)
2973
2974
2975
2976
2977
2978 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2979
2980 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2981 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2982
2983 if _cfg.get(option = 'debug'):
2984 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
2985 _log.debug('connected old signal monitor')
2986 wx.lib.pubsub.Publisher().subscribe(listener = self._signal_debugging_monitor_pubsub)
2987 _log.debug('connected wx.lib.pubsub based signal monitor for all topics')
2988
2989
2990
2991
2992
2993
3009
3011 """Handle all the database related tasks necessary for startup."""
3012
3013
3014 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
3015
3016 from Gnumed.wxpython import gmAuthWidgets
3017 connected = gmAuthWidgets.connect_to_database (
3018 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
3019 require_version = not override
3020 )
3021 if not connected:
3022 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
3023 return False
3024
3025
3026 try:
3027 global _provider
3028 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
3029 except ValueError:
3030 account = gmPG2.get_current_user()
3031 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
3032 msg = _(
3033 'The database account [%s] cannot be used as a\n'
3034 'staff member login for GNUmed. There was an\n'
3035 'error retrieving staff details for it.\n\n'
3036 'Please ask your administrator for help.\n'
3037 ) % account
3038 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
3039 return False
3040
3041
3042 tmp = '%s%s %s (%s = %s)' % (
3043 gmTools.coalesce(_provider['title'], ''),
3044 _provider['firstnames'],
3045 _provider['lastnames'],
3046 _provider['short_alias'],
3047 _provider['db_user']
3048 )
3049 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
3050
3051
3052 surgery = gmSurgery.gmCurrentPractice()
3053 msg = surgery.db_logon_banner
3054 if msg.strip() != u'':
3055
3056 login = gmPG2.get_default_login()
3057 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
3058 login.database,
3059 gmTools.coalesce(login.host, u'localhost')
3060 ))
3061 msg = auth + msg + u'\n\n'
3062
3063 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3064 None,
3065
3066 -1,
3067 caption = _('Verifying database'),
3068 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3069 button_defs = [
3070 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3071 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3072 ]
3073 )
3074 go_on = dlg.ShowModal()
3075 dlg.Destroy()
3076 if go_on != wx.ID_YES:
3077 _log.info('user decided to not connect to this database')
3078 return False
3079
3080
3081 self.__check_db_lang()
3082
3083 return True
3084
3086 """Setup access to a config file for storing preferences."""
3087
3088 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3089
3090 candidates = []
3091 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3092 if explicit_file is not None:
3093 candidates.append(explicit_file)
3094
3095 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3096 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3097 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3098
3099 prefs_file = None
3100 for candidate in candidates:
3101 try:
3102 open(candidate, 'a+').close()
3103 prefs_file = candidate
3104 break
3105 except IOError:
3106 continue
3107
3108 if prefs_file is None:
3109 msg = _(
3110 'Cannot find configuration file in any of:\n'
3111 '\n'
3112 ' %s\n'
3113 'You may need to use the comand line option\n'
3114 '\n'
3115 ' --conf-file=<FILE>'
3116 ) % '\n '.join(candidates)
3117 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3118 return False
3119
3120 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3121 _log.info('user preferences file: %s', prefs_file)
3122
3123 return True
3124
3126
3127 from socket import error as SocketError
3128 from Gnumed.pycommon import gmScriptingListener
3129 from Gnumed.wxpython import gmMacro
3130
3131 slave_personality = gmTools.coalesce (
3132 _cfg.get (
3133 group = u'workplace',
3134 option = u'slave personality',
3135 source_order = [
3136 ('explicit', 'return'),
3137 ('workbase', 'return'),
3138 ('user', 'return'),
3139 ('system', 'return')
3140 ]
3141 ),
3142 u'gnumed-client'
3143 )
3144 _cfg.set_option(option = 'slave personality', value = slave_personality)
3145
3146
3147 port = int (
3148 gmTools.coalesce (
3149 _cfg.get (
3150 group = u'workplace',
3151 option = u'xml-rpc port',
3152 source_order = [
3153 ('explicit', 'return'),
3154 ('workbase', 'return'),
3155 ('user', 'return'),
3156 ('system', 'return')
3157 ]
3158 ),
3159 9999
3160 )
3161 )
3162 _cfg.set_option(option = 'xml-rpc port', value = port)
3163
3164 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3165 global _scripting_listener
3166 try:
3167 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3168 except SocketError, e:
3169 _log.exception('cannot start GNUmed XML-RPC server')
3170 gmGuiHelpers.gm_show_error (
3171 aMessage = (
3172 'Cannot start the GNUmed server:\n'
3173 '\n'
3174 ' [%s]'
3175 ) % e,
3176 aTitle = _('GNUmed startup')
3177 )
3178 return False
3179
3180 return True
3181
3202
3204 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3205 _log.warning("system locale is undefined (probably meaning 'C')")
3206 return True
3207
3208
3209 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3210 db_lang = rows[0]['lang']
3211
3212 if db_lang is None:
3213 _log.debug("database locale currently not set")
3214 msg = _(
3215 "There is no language selected in the database for user [%s].\n"
3216 "Your system language is currently set to [%s].\n\n"
3217 "Do you want to set the database language to '%s' ?\n\n"
3218 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3219 checkbox_msg = _('Remember to ignore missing language')
3220 else:
3221 _log.debug("current database locale: [%s]" % db_lang)
3222 msg = _(
3223 "The currently selected database language ('%s') does\n"
3224 "not match the current system language ('%s').\n"
3225 "\n"
3226 "Do you want to set the database language to '%s' ?\n"
3227 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3228 checkbox_msg = _('Remember to ignore language mismatch')
3229
3230
3231 if db_lang == gmI18N.system_locale_level['full']:
3232 _log.debug('Database locale (%s) up to date.' % db_lang)
3233 return True
3234 if db_lang == gmI18N.system_locale_level['country']:
3235 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3236 return True
3237 if db_lang == gmI18N.system_locale_level['language']:
3238 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3239 return True
3240
3241 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3242
3243
3244 ignored_sys_lang = _cfg.get (
3245 group = u'backend',
3246 option = u'ignored mismatching system locale',
3247 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3248 )
3249
3250
3251 if gmI18N.system_locale == ignored_sys_lang:
3252 _log.info('configured to ignore system-to-database locale mismatch')
3253 return True
3254
3255
3256 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3257 None,
3258 -1,
3259 caption = _('Checking database language settings'),
3260 question = msg,
3261 button_defs = [
3262 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3263 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3264 ],
3265 show_checkbox = True,
3266 checkbox_msg = checkbox_msg,
3267 checkbox_tooltip = _(
3268 'Checking this will make GNUmed remember your decision\n'
3269 'until the system language is changed.\n'
3270 '\n'
3271 'You can also reactivate this inquiry by removing the\n'
3272 'corresponding "ignore" option from the configuration file\n'
3273 '\n'
3274 ' [%s]'
3275 ) % _cfg.get(option = 'user_preferences_file')
3276 )
3277 decision = dlg.ShowModal()
3278 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3279 dlg.Destroy()
3280
3281 if decision == wx.ID_NO:
3282 if not remember_ignoring_problem:
3283 return True
3284 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3285 gmCfg2.set_option_in_INI_file (
3286 filename = _cfg.get(option = 'user_preferences_file'),
3287 group = 'backend',
3288 option = 'ignored mismatching system locale',
3289 value = gmI18N.system_locale
3290 )
3291 return True
3292
3293
3294 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3295 if len(lang) > 0:
3296
3297
3298 rows, idx = gmPG2.run_rw_queries (
3299 link_obj = None,
3300 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3301 return_data = True
3302 )
3303 if rows[0][0]:
3304 _log.debug("Successfully set database language to [%s]." % lang)
3305 else:
3306 _log.error('Cannot set database language to [%s].' % lang)
3307 continue
3308 return True
3309
3310
3311 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3312 gmPG2.run_rw_queries(queries = [{
3313 'cmd': u'select i18n.force_curr_lang(%s)',
3314 'args': [gmI18N.system_locale_level['country']]
3315 }])
3316
3317 return True
3318
3320 try:
3321 kwargs['originated_in_database']
3322 print '==> got notification from database "%s":' % kwargs['signal']
3323 except KeyError:
3324 print '==> received signal from client: "%s"' % kwargs['signal']
3325
3326 del kwargs['signal']
3327 for key in kwargs.keys():
3328
3329 try: print ' [%s]: %s' % (key, kwargs[key])
3330 except: print 'cannot print signal information'
3331
3333
3334 try:
3335 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3336 print ' data: %s' % msg.data
3337 print msg
3338 except: print 'problem printing pubsub message information'
3339
3341
3342 if _cfg.get(option = 'debug'):
3343 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3344 _log.debug('gmDispatcher signal monitor activated')
3345 wx.lib.pubsub.Publisher().subscribe (
3346 listener = _signal_debugging_monitor_pubsub
3347
3348 )
3349 _log.debug('wx.lib.pubsub signal monitor activated')
3350
3351 wx.InitAllImageHandlers()
3352
3353
3354
3355 app = gmApp(redirect = False, clearSigInt = False)
3356 app.MainLoop()
3357
3358
3359
3360 if __name__ == '__main__':
3361
3362 from GNUmed.pycommon import gmI18N
3363 gmI18N.activate_locale()
3364 gmI18N.install_domain()
3365
3366 _log.info('Starting up as main module.')
3367 main()
3368
3369
3370