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 copyright: authors
10 """
11
12 __version__ = "$Revision: 1.491 $"
13 __author__ = "H. Herb <hherb@gnumed.net>,\
14 K. Hilbert <Karsten.Hilbert@gmx.net>,\
15 I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
16 __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
17
18
19 import sys, time, os, locale, os.path, datetime as pyDT
20 import shutil, logging, urllib2, subprocess, glob
21
22
23
24
25 if not hasattr(sys, 'frozen'):
26 import wxversion
27 wxversion.ensureMinimal('2.8-unicode', optionsRequired=True)
28
29 try:
30 import wx
31 import wx.lib.pubsub
32 except ImportError:
33 print "GNUmed startup: Cannot import wxPython library."
34 print "GNUmed startup: Make sure wxPython is installed."
35 print 'CRITICAL ERROR: Error importing wxPython. Halted.'
36 raise
37
38
39
40 version = int(u'%s%s' % (wx.MAJOR_VERSION, wx.MINOR_VERSION))
41 if (version < 28) or ('unicode' not in wx.PlatformInfo):
42 print "GNUmed startup: Unsupported wxPython version (%s: %s)." % (wx.VERSION_STRING, wx.PlatformInfo)
43 print "GNUmed startup: wxPython 2.8+ with unicode support is required."
44 print 'CRITICAL ERROR: Proper wxPython version not found. Halted.'
45 raise ValueError('wxPython 2.8+ with unicode support not found')
46
47
48
49 from Gnumed.pycommon import gmCfg, gmPG2, gmDispatcher, gmGuiBroker, gmI18N
50 from Gnumed.pycommon import gmExceptions, gmShellAPI, gmTools, gmDateTime
51 from Gnumed.pycommon import gmHooks, gmBackendListener, gmCfg2, gmLog2, gmNetworkTools
52
53 from Gnumed.business import gmPerson, gmClinicalRecord, gmSurgery, gmEMRStructItems
54 from Gnumed.business import gmVaccination
55 from Gnumed.business import gmArriba
56 from Gnumed.business import gmStaff
57
58 from Gnumed.exporters import gmPatientExporter
59
60 from Gnumed.wxpython import gmGuiHelpers, gmHorstSpace, gmEMRBrowser
61 from Gnumed.wxpython import gmDemographicsWidgets, gmEMRStructWidgets
62 from Gnumed.wxpython import gmPatSearchWidgets, gmAllergyWidgets, gmListWidgets
63 from Gnumed.wxpython import gmProviderInboxWidgets, gmCfgWidgets, gmExceptionHandlingWidgets
64 from Gnumed.wxpython import gmNarrativeWidgets, gmPhraseWheel, gmMedicationWidgets
65 from Gnumed.wxpython import gmStaffWidgets, gmDocumentWidgets, gmTimer, gmMeasurementWidgets
66 from Gnumed.wxpython import gmFormWidgets, gmSnellen
67 from Gnumed.wxpython import gmVaccWidgets
68 from Gnumed.wxpython import gmPersonContactWidgets
69 from Gnumed.wxpython import gmI18nWidgets
70 from Gnumed.wxpython import gmCodingWidgets
71 from Gnumed.wxpython import gmOrganizationWidgets
72 from Gnumed.wxpython import gmAuthWidgets
73 from Gnumed.wxpython import gmFamilyHistoryWidgets
74 from Gnumed.wxpython import gmDataPackWidgets
75 from Gnumed.wxpython import gmContactWidgets
76 from Gnumed.wxpython import gmAddressWidgets
77
78
79 try:
80 _('dummy-no-need-to-translate-but-make-epydoc-happy')
81 except NameError:
82 _ = lambda x:x
83
84 _cfg = gmCfg2.gmCfgData()
85 _provider = None
86 _scripting_listener = None
87
88 _log = logging.getLogger('gm.main')
89 _log.info(__version__)
90 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
91
92
94 """GNUmed client's main windows frame.
95
96 This is where it all happens. Avoid popping up any other windows.
97 Most user interaction should happen to and from widgets within this frame
98 """
99
100 - def __init__(self, parent, id, title, size=wx.DefaultSize):
101 """You'll have to browse the source to understand what the constructor does
102 """
103 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
104
105 self.__setup_font()
106
107 self.__gb = gmGuiBroker.GuiBroker()
108 self.__pre_exit_callbacks = []
109 self.bar_width = -1
110 self.menu_id2plugin = {}
111
112 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
113
114 self.__setup_main_menu()
115 self.setup_statusbar()
116 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
117 gmTools.coalesce(_provider['title'], ''),
118 _provider['firstnames'][:1],
119 _provider['lastnames'],
120 _provider['short_alias'],
121 _provider['db_user']
122 ))
123
124 self.__set_window_title_template()
125 self.__update_window_title()
126
127
128
129
130
131 self.SetIcon(gmTools.get_icon(wx = wx))
132
133 self.__register_events()
134
135 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
136 self.vbox = wx.BoxSizer(wx.VERTICAL)
137 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
138
139 self.SetAutoLayout(True)
140 self.SetSizerAndFit(self.vbox)
141
142
143
144
145
146 self.__set_GUI_size()
147
148
150
151 font = self.GetFont()
152 _log.debug('system default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
153
154 desired_font_face = _cfg.get (
155 group = u'workplace',
156 option = u'client font',
157 source_order = [
158 ('explicit', 'return'),
159 ('workbase', 'return'),
160 ('local', 'return'),
161 ('user', 'return'),
162 ('system', 'return')
163 ]
164 )
165
166 fonts2try = []
167 if desired_font_face is not None:
168 _log.info('client is configured to use font [%s]', desired_font_face)
169 fonts2try.append(desired_font_face)
170
171 if wx.Platform == '__WXMSW__':
172 sane_font_face = u'DejaVu Sans'
173 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face)
174 fonts2try.append(sane_font_face)
175
176 if len(fonts2try) == 0:
177 return
178
179 for font_face in fonts2try:
180 success = font.SetFaceName(font_face)
181 if success:
182 self.SetFont(font)
183 _log.debug('switched font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
184 return
185 font = self.GetFont()
186 _log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face)
187
188 return
189
191 """Try to get previous window size from backend."""
192
193 cfg = gmCfg.cCfgSQL()
194
195
196 width = int(cfg.get2 (
197 option = 'main.window.width',
198 workplace = gmSurgery.gmCurrentPractice().active_workplace,
199 bias = 'workplace',
200 default = 800
201 ))
202
203
204 height = int(cfg.get2 (
205 option = 'main.window.height',
206 workplace = gmSurgery.gmCurrentPractice().active_workplace,
207 bias = 'workplace',
208 default = 600
209 ))
210
211 dw = wx.DisplaySize()[0]
212 dh = wx.DisplaySize()[1]
213
214 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
215 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
216 _log.debug('previous GUI size [%s:%s]', width, height)
217
218
219 if width > dw:
220 _log.debug('adjusting GUI width from %s to %s', width, dw)
221 width = dw
222
223 if height > dh:
224 _log.debug('adjusting GUI height from %s to %s', height, dh)
225 height = dh
226
227
228 if width < 100:
229 _log.debug('adjusting GUI width to minimum of 100 pixel')
230 width = 100
231 if height < 100:
232 _log.debug('adjusting GUI height to minimum of 100 pixel')
233 height = 100
234
235 _log.info('setting GUI to size [%s:%s]', width, height)
236
237 self.SetClientSize(wx.Size(width, height))
238
240 """Create the main menu entries.
241
242 Individual entries are farmed out to the modules.
243
244 menu item template:
245
246 item = menu_emr_edit.Append(-1, _(''), _(''))
247 self.Bind(wx.EVT_MENU, self__on_, item)
248 """
249 global wx
250 self.mainmenu = wx.MenuBar()
251 self.__gb['main.mainmenu'] = self.mainmenu
252
253
254 menu_gnumed = wx.Menu()
255
256 self.menu_plugins = wx.Menu()
257 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
258
259 ID = wx.NewId()
260 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
261 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
262
263 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
264 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
265
266
267 menu_gnumed.AppendSeparator()
268
269
270 menu_config = wx.Menu()
271
272 item = menu_config.Append(-1, _('List configuration'), _('List all configuration items stored in the database.'))
273 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
274
275
276 menu_cfg_db = wx.Menu()
277
278 ID = wx.NewId()
279 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
280 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
281
282 ID = wx.NewId()
283 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
284 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
285
286 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
287
288
289 menu_cfg_client = wx.Menu()
290
291 ID = wx.NewId()
292 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
293 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
294
295 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
296 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
297
298 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
299
300
301 menu_cfg_ui = wx.Menu()
302
303
304 menu_cfg_doc = wx.Menu()
305
306 ID = wx.NewId()
307 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
308 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
309
310 ID = wx.NewId()
311 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
312 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
313
314 ID = wx.NewId()
315 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
316 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
317
318 item = menu_cfg_doc.Append(-1, _('Generate UUID'), _('Whether to generate UUIDs for new documents.'))
319 self.Bind(wx.EVT_MENU, self.__on_configure_generate_doc_uuid, item)
320
321 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
322
323
324 menu_cfg_update = wx.Menu()
325
326 ID = wx.NewId()
327 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
328 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
329
330 ID = wx.NewId()
331 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
332 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
333
334 ID = wx.NewId()
335 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
336 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
337
338 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
339
340
341 menu_cfg_pat_search = wx.Menu()
342
343 ID = wx.NewId()
344 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
345 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
346
347 ID = wx.NewId()
348 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
349 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
350
351 ID = wx.NewId()
352 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
353 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
354
355 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
356 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
357
358 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
359 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
360
361 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
362
363
364 menu_cfg_soap_editing = wx.Menu()
365
366 ID = wx.NewId()
367 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
368 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
369
370 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.'))
371 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item)
372
373 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
374
375 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
376
377
378 menu_cfg_ext_tools = wx.Menu()
379
380
381
382
383
384 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
385 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
386
387 ID = wx.NewId()
388 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
389 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
390
391 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
392 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
393
394 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
395 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
396
397 item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.'))
398 self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item)
399
400 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
401 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
402
403 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
404 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
405
406 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
407 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
408
409 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
410 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
411
412 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
413
414
415 menu_cfg_emr = wx.Menu()
416
417 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
418 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
419
420 item = menu_cfg_emr.Append(-1, _('Primary doctor'), _('Select the primary doctor to fall back to for patients without one.'))
421 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
422
423
424 menu_cfg_encounter = wx.Menu()
425
426 ID = wx.NewId()
427 menu_cfg_encounter.Append(ID, _('Edit before patient change'), _('Edit encounter details before change of patient.'))
428 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
429
430 ID = wx.NewId()
431 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
432 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
433
434 ID = wx.NewId()
435 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
436 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
437
438 ID = wx.NewId()
439 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
440 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
441
442 ID = wx.NewId()
443 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
444 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
445
446 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
447
448
449 menu_cfg_episode = wx.Menu()
450
451 ID = wx.NewId()
452 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
453 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
454
455 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
456 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
457 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
458
459
460 menu_master_data = wx.Menu()
461
462 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
463 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
464
465 item = menu_master_data.Append(-1, _('Install data packs'), _('Install reference data from data packs.'))
466 self.Bind(wx.EVT_MENU, self.__on_install_data_packs, item)
467
468 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
469 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
470
471
472
473
474 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
475 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
476
477 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
478
479
480 menu_users = wx.Menu()
481
482 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
483 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
484
485 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
486 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
487
488 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
489 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
490
491 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
492
493
494 menu_gnumed.AppendSeparator()
495
496 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
497 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
498
499 self.mainmenu.Append(menu_gnumed, '&GNUmed')
500
501
502 menu_person = wx.Menu()
503
504 ID_CREATE_PATIENT = wx.NewId()
505 menu_person.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
506 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
507
508 ID_LOAD_EXT_PAT = wx.NewId()
509 menu_person.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
510 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
511
512 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
513 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
514
515 ID_DEL_PAT = wx.NewId()
516 menu_person.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
517 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
518
519 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
520 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
521
522 menu_person.AppendSeparator()
523
524 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
525 menu_person.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
526 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
527
528
529 ID = wx.NewId()
530 menu_person.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
531 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
532
533 menu_person.AppendSeparator()
534
535 self.mainmenu.Append(menu_person, '&Person')
536 self.__gb['main.patientmenu'] = menu_person
537
538
539 menu_emr = wx.Menu()
540
541
542 menu_emr_edit = wx.Menu()
543
544 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'))
545 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
546
547 item = menu_emr_edit.Append(-1, _('&Episode'), _('Add an episode of illness to the EMR of the active patient'))
548 self.Bind(wx.EVT_MENU, self.__on_add_episode, item)
549
550 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
551 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
552
553 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
554 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
555
556 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
557 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
558
559 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
560 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
561
562 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
563 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
564
565 item = menu_emr_edit.Append(-1, _('&Measurements'), _('Add (a) measurement result(s) for the current patient.'))
566 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
567
568 item = menu_emr_edit.Append(-1, _('&Vaccinations'), _('Add (a) vaccination(s) for the current patient.'))
569 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
570
571 item = menu_emr_edit.Append(-1, _('&Family history (FHx)'), _('Manage family history.'))
572 self.Bind(wx.EVT_MENU, self.__on_manage_fhx, item)
573
574 item = menu_emr_edit.Append(-1, _('&Encounters'), _('List all encounters including empty ones.'))
575 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
576
577 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
578
579
580 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
581 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
582
583 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
584 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
585
586 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
587 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
588
589
590
591
592
593 item = menu_emr.Append(-1, _('Statistics'), _('Show a high-level statistic summary of the EMR.'))
594 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
595
596
597
598
599 menu_emr.AppendSeparator()
600
601
602 menu_emr_export = wx.Menu()
603
604 ID_EXPORT_EMR_ASCII = wx.NewId()
605 menu_emr_export.Append (
606 ID_EXPORT_EMR_ASCII,
607 _('Text document'),
608 _("Export the EMR of the active patient into a text file")
609 )
610 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
611
612 ID_EXPORT_EMR_JOURNAL = wx.NewId()
613 menu_emr_export.Append (
614 ID_EXPORT_EMR_JOURNAL,
615 _('Journal'),
616 _("Export the EMR of the active patient as a chronological journal into a text file")
617 )
618 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
619
620 ID_EXPORT_MEDISTAR = wx.NewId()
621 menu_emr_export.Append (
622 ID_EXPORT_MEDISTAR,
623 _('MEDISTAR import format'),
624 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
625 )
626 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
627
628 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
629
630 menu_emr.AppendSeparator()
631
632 self.mainmenu.Append(menu_emr, _("&EMR"))
633 self.__gb['main.emrmenu'] = menu_emr
634
635
636 menu_paperwork = wx.Menu()
637
638 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
639 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
640
641 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
642
643
644 self.menu_tools = wx.Menu()
645
646 ID_DICOM_VIEWER = wx.NewId()
647 viewer = _('no viewer installed')
648 if gmShellAPI.detect_external_binary(binary = 'ginkgocadx')[0]:
649 viewer = u'Ginkgo CADx'
650 elif os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
651 viewer = u'OsiriX'
652 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
653 viewer = u'Aeskulap'
654 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
655 viewer = u'AMIDE'
656 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
657 viewer = u'DicomScope'
658 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
659 viewer = u'(x)medcon'
660 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)
661 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
662 if viewer == _('no viewer installed'):
663 _log.info('neither of Ginkgo CADx / OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
664 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
665
666
667
668
669
670 ID = wx.NewId()
671 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
672 wx.EVT_MENU(self, ID, self.__on_snellen)
673
674 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
675 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
676
677 item = self.menu_tools.Append(-1, _('arriba'), _('arriba: cardiovascular risk assessment (%s).') % u'www.arriba-hausarzt.de')
678 self.Bind(wx.EVT_MENU, self.__on_arriba, item)
679
680 self.menu_tools.AppendSeparator()
681
682 self.mainmenu.Append(self.menu_tools, _("&Tools"))
683 self.__gb['main.toolsmenu'] = self.menu_tools
684
685
686 menu_knowledge = wx.Menu()
687
688
689 menu_drug_dbs = wx.Menu()
690
691 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
692 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
693
694
695
696
697
698
699 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
700
701 menu_id = wx.NewId()
702 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
703 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
704
705
706
707
708 ID_MEDICAL_LINKS = wx.NewId()
709 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
710 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
711
712 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
713 self.__gb['main.knowledgemenu'] = menu_knowledge
714
715
716 self.menu_office = wx.Menu()
717
718 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
719 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
720
721 self.menu_office.AppendSeparator()
722
723 self.mainmenu.Append(self.menu_office, _('&Office'))
724 self.__gb['main.officemenu'] = self.menu_office
725
726
727 help_menu = wx.Menu()
728
729 ID = wx.NewId()
730 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
731 wx.EVT_MENU(self, ID, self.__on_display_wiki)
732
733 ID = wx.NewId()
734 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
735 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
736
737 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
738 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
739
740 menu_debugging = wx.Menu()
741
742 ID_SCREENSHOT = wx.NewId()
743 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
744 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
745
746 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
747 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
748
749 ID = wx.NewId()
750 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
751 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
752
753 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
754 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
755
756 ID = wx.NewId()
757 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
758 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
759
760 ID_UNBLOCK = wx.NewId()
761 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
762 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
763
764 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
765 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
766
767
768
769
770 if _cfg.get(option = 'debug'):
771 ID_TOGGLE_PAT_LOCK = wx.NewId()
772 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
773 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
774
775 ID_TEST_EXCEPTION = wx.NewId()
776 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
777 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
778
779 ID = wx.NewId()
780 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
781 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
782 try:
783 import wx.lib.inspection
784 except ImportError:
785 menu_debugging.Enable(id = ID, enable = False)
786
787 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
788
789 help_menu.AppendSeparator()
790
791 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
792 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
793
794 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
795 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
796
797 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors'))
798 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item)
799
800 help_menu.AppendSeparator()
801
802 self.mainmenu.Append(help_menu, _("&Help"))
803
804 self.__gb['main.helpmenu'] = help_menu
805
806
807 self.SetMenuBar(self.mainmenu)
808
811
812
813
815 """register events we want to react to"""
816
817 wx.EVT_CLOSE(self, self.OnClose)
818 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
819 wx.EVT_END_SESSION(self, self._on_end_session)
820
821 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
822 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
823 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
824 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
825 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
826 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
827 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
828 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
829
830 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
831
832 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
833
834 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
835
836 _log.debug('registering plugin with menu system')
837 _log.debug(' generic name: %s', plugin_name)
838 _log.debug(' class name: %s', class_name)
839 _log.debug(' specific menu: %s', menu_name)
840 _log.debug(' menu item: %s', menu_item_name)
841
842
843 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
844 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
845 self.menu_id2plugin[item.Id] = class_name
846
847
848 if menu_name is not None:
849 menu = self.__gb['main.%smenu' % menu_name]
850 item = menu.Append(-1, menu_item_name, menu_help_string)
851 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
852 self.menu_id2plugin[item.Id] = class_name
853
854 return True
855
857 gmDispatcher.send (
858 signal = u'display_widget',
859 name = self.menu_id2plugin[evt.Id]
860 )
861
863 wx.Bell()
864 wx.Bell()
865 wx.Bell()
866 _log.warning('unhandled event detected: QUERY_END_SESSION')
867 _log.info('we should be saving ourselves from here')
868 gmLog2.flush()
869 print "unhandled event detected: QUERY_END_SESSION"
870
872 wx.Bell()
873 wx.Bell()
874 wx.Bell()
875 _log.warning('unhandled event detected: END_SESSION')
876 gmLog2.flush()
877 print "unhandled event detected: END_SESSION"
878
880 if not callable(callback):
881 raise TypeError(u'callback [%s] not callable' % callback)
882
883 self.__pre_exit_callbacks.append(callback)
884
885 - def _on_set_statustext_pubsub(self, context=None):
886 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
887 wx.CallAfter(self.SetStatusText, msg)
888
889 try:
890 if context.data['beep']:
891 wx.Bell()
892 except KeyError:
893 pass
894
895 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
896
897 if msg is None:
898 msg = _('programmer forgot to specify status message')
899
900 if loglevel is not None:
901 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
902
903 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
904 wx.CallAfter(self.SetStatusText, msg)
905
906 if beep:
907 wx.Bell()
908
910 wx.CallAfter(self.__on_db_maintenance_warning)
911
913
914 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
915 wx.Bell()
916 if not wx.GetApp().IsActive():
917 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
918
919 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
920
921 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
922 None,
923 -1,
924 caption = _('Database shutdown warning'),
925 question = _(
926 'The database will be shut down for maintenance\n'
927 'in a few minutes.\n'
928 '\n'
929 'In order to not suffer any loss of data you\n'
930 'will need to save your current work and log\n'
931 'out of this GNUmed client.\n'
932 ),
933 button_defs = [
934 {
935 u'label': _('Close now'),
936 u'tooltip': _('Close this GNUmed client immediately.'),
937 u'default': False
938 },
939 {
940 u'label': _('Finish work'),
941 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
942 u'default': True
943 }
944 ]
945 )
946 decision = dlg.ShowModal()
947 if decision == wx.ID_YES:
948 top_win = wx.GetApp().GetTopWindow()
949 wx.CallAfter(top_win.Close)
950
952 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
953
955
956 if not wx.GetApp().IsActive():
957 if urgent:
958 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
959 else:
960 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
961
962 if msg is not None:
963 self.SetStatusText(msg)
964
965 if urgent:
966 wx.Bell()
967
968 gmHooks.run_hook_script(hook = u'request_user_attention')
969
971 wx.CallAfter(self.__on_pat_name_changed)
972
974 self.__update_window_title()
975
977 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
978
980 self.__update_window_title()
981 try:
982 gmHooks.run_hook_script(hook = u'post_patient_activation')
983 except:
984 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
985 raise
986
988 return self.__sanity_check_encounter()
989
1051
1052
1053
1056
1064
1065
1066
1081
1104
1106 from Gnumed.wxpython import gmAbout
1107 contribs = gmAbout.cContributorsDlg (
1108 parent = self,
1109 id = -1,
1110 title = _('GNUmed contributors'),
1111 size = wx.Size(400,600),
1112 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1113 )
1114 contribs.ShowModal()
1115 del contribs
1116 del gmAbout
1117
1118
1119
1121 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1122 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1123 self.Close(True)
1124 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1125
1128
1130 send = gmGuiHelpers.gm_show_question (
1131 _('This will send a notification about database downtime\n'
1132 'to all GNUmed clients connected to your database.\n'
1133 '\n'
1134 'Do you want to send the notification ?\n'
1135 ),
1136 _('Announcing database maintenance downtime')
1137 )
1138 if not send:
1139 return
1140 gmPG2.send_maintenance_notification()
1141
1142
1145
1146
1147
1160
1161 gmCfgWidgets.configure_string_option (
1162 message = _(
1163 'Some network installations cannot cope with loading\n'
1164 'documents of arbitrary size in one piece from the\n'
1165 'database (mainly observed on older Windows versions)\n.'
1166 '\n'
1167 'Under such circumstances documents need to be retrieved\n'
1168 'in chunks and reassembled on the client.\n'
1169 '\n'
1170 'Here you can set the size (in Bytes) above which\n'
1171 'GNUmed will retrieve documents in chunks. Setting this\n'
1172 'value to 0 will disable the chunking protocol.'
1173 ),
1174 option = 'horstspace.blob_export_chunk_size',
1175 bias = 'workplace',
1176 default_value = 1024 * 1024,
1177 validator = is_valid
1178 )
1179
1180
1181
1249
1253
1254
1255
1264
1265 gmCfgWidgets.configure_string_option (
1266 message = _(
1267 'When GNUmed cannot find an OpenOffice server it\n'
1268 'will try to start one. OpenOffice, however, needs\n'
1269 'some time to fully start up.\n'
1270 '\n'
1271 'Here you can set the time for GNUmed to wait for OOo.\n'
1272 ),
1273 option = 'external.ooo.startup_settle_time',
1274 bias = 'workplace',
1275 default_value = 2.0,
1276 validator = is_valid
1277 )
1278
1281
1296
1297 gmCfgWidgets.configure_string_option (
1298 message = _(
1299 'GNUmed will use this URL to access a website which lets\n'
1300 'you report an adverse drug reaction (ADR).\n'
1301 '\n'
1302 'If you leave this empty it will fall back\n'
1303 'to an URL for reporting ADRs in Germany.'
1304 ),
1305 option = 'external.urls.report_ADR',
1306 bias = 'user',
1307 default_value = german_default,
1308 validator = is_valid
1309 )
1310
1324
1325 gmCfgWidgets.configure_string_option (
1326 message = _(
1327 'GNUmed will use this URL to access a website which lets\n'
1328 'you report an adverse vaccination reaction (vADR).\n'
1329 '\n'
1330 'If you set it to a specific address that URL must be\n'
1331 'accessible now. If you leave it empty it will fall back\n'
1332 'to the URL for reporting other adverse drug reactions.'
1333 ),
1334 option = 'external.urls.report_vaccine_ADR',
1335 bias = 'user',
1336 default_value = german_default,
1337 validator = is_valid
1338 )
1339
1353
1354 gmCfgWidgets.configure_string_option (
1355 message = _(
1356 'GNUmed will use this URL to access an encyclopedia of\n'
1357 'measurement/lab methods from within the measurments grid.\n'
1358 '\n'
1359 'You can leave this empty but to set it to a specific\n'
1360 'address the URL must be accessible now.'
1361 ),
1362 option = 'external.urls.measurements_encyclopedia',
1363 bias = 'user',
1364 default_value = german_default,
1365 validator = is_valid
1366 )
1367
1381
1382 gmCfgWidgets.configure_string_option (
1383 message = _(
1384 'GNUmed will use this URL to access a page showing\n'
1385 'vaccination schedules.\n'
1386 '\n'
1387 'You can leave this empty but to set it to a specific\n'
1388 'address the URL must be accessible now.'
1389 ),
1390 option = 'external.urls.vaccination_plans',
1391 bias = 'user',
1392 default_value = german_default,
1393 validator = is_valid
1394 )
1395
1408
1409 gmCfgWidgets.configure_string_option (
1410 message = _(
1411 'Enter the shell command with which to start the\n'
1412 'the ACS risk assessment calculator.\n'
1413 '\n'
1414 'GNUmed will try to verify the path which may,\n'
1415 'however, fail if you are using an emulator such\n'
1416 'as Wine. Nevertheless, starting the calculator\n'
1417 'will work as long as the shell command is correct\n'
1418 'despite the failing test.'
1419 ),
1420 option = 'external.tools.acs_risk_calculator_cmd',
1421 bias = 'user',
1422 validator = is_valid
1423 )
1424
1427
1440
1441 gmCfgWidgets.configure_string_option (
1442 message = _(
1443 'Enter the shell command with which to start\n'
1444 'the FreeDiams drug database frontend.\n'
1445 '\n'
1446 'GNUmed will try to verify that path.'
1447 ),
1448 option = 'external.tools.freediams_cmd',
1449 bias = 'workplace',
1450 default_value = None,
1451 validator = is_valid
1452 )
1453
1466
1467 gmCfgWidgets.configure_string_option (
1468 message = _(
1469 'Enter the shell command with which to start the\n'
1470 'the IFAP drug database.\n'
1471 '\n'
1472 'GNUmed will try to verify the path which may,\n'
1473 'however, fail if you are using an emulator such\n'
1474 'as Wine. Nevertheless, starting IFAP will work\n'
1475 'as long as the shell command is correct despite\n'
1476 'the failing test.'
1477 ),
1478 option = 'external.ifap-win.shell_command',
1479 bias = 'workplace',
1480 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1481 validator = is_valid
1482 )
1483
1484
1485
1534
1535
1536
1553
1556
1559
1564
1565 gmCfgWidgets.configure_string_option (
1566 message = _(
1567 'When a patient is activated GNUmed checks the\n'
1568 "proximity of the patient's birthday.\n"
1569 '\n'
1570 'If the birthday falls within the range of\n'
1571 ' "today %s <the interval you set here>"\n'
1572 'GNUmed will remind you of the recent or\n'
1573 'imminent anniversary.'
1574 ) % u'\u2213',
1575 option = u'patient_search.dob_warn_interval',
1576 bias = 'user',
1577 default_value = '1 week',
1578 validator = is_valid
1579 )
1580
1582
1583 gmCfgWidgets.configure_boolean_option (
1584 parent = self,
1585 question = _(
1586 'When adding progress notes do you want to\n'
1587 'allow opening several unassociated, new\n'
1588 'episodes for a patient at once ?\n'
1589 '\n'
1590 'This can be particularly helpful when entering\n'
1591 'progress notes on entirely new patients presenting\n'
1592 'with a multitude of problems on their first visit.'
1593 ),
1594 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1595 button_tooltips = [
1596 _('Yes, allow for multiple new episodes concurrently.'),
1597 _('No, only allow editing one new episode at a time.')
1598 ]
1599 )
1600
1602
1603 gmCfgWidgets.configure_boolean_option (
1604 parent = self,
1605 question = _(
1606 'When activating a patient, do you want GNUmed to\n'
1607 'auto-open editors for all active problems that were\n'
1608 'touched upon during the current and the most recent\n'
1609 'encounter ?'
1610 ),
1611 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1612 button_tooltips = [
1613 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1614 _('No, only auto-open one editor for a new, unassociated problem.')
1615 ]
1616 )
1617
1663
1664
1665
1668
1671
1685
1687 gmCfgWidgets.configure_boolean_option (
1688 parent = self,
1689 question = _(
1690 'Do you want GNUmed to show the encounter\n'
1691 'details editor when changing the active patient ?'
1692 ),
1693 option = 'encounter.show_editor_before_patient_change',
1694 button_tooltips = [
1695 _('Yes, show the encounter editor if it seems appropriate.'),
1696 _('No, never show the encounter editor even if it would seem useful.')
1697 ]
1698 )
1699
1704
1705 gmCfgWidgets.configure_string_option (
1706 message = _(
1707 'When a patient is activated GNUmed checks the\n'
1708 'chart for encounters lacking any entries.\n'
1709 '\n'
1710 'Any such encounters older than what you set\n'
1711 'here will be removed from the medical record.\n'
1712 '\n'
1713 'To effectively disable removal of such encounters\n'
1714 'set this option to an improbable value.\n'
1715 ),
1716 option = 'encounter.ttl_if_empty',
1717 bias = 'user',
1718 default_value = '1 week',
1719 validator = is_valid
1720 )
1721
1726
1727 gmCfgWidgets.configure_string_option (
1728 message = _(
1729 'When a patient is activated GNUmed checks the\n'
1730 'age of the most recent encounter.\n'
1731 '\n'
1732 'If that encounter is younger than this age\n'
1733 'the existing encounter will be continued.\n'
1734 '\n'
1735 '(If it is really old a new encounter is\n'
1736 ' started, or else GNUmed will ask you.)\n'
1737 ),
1738 option = 'encounter.minimum_ttl',
1739 bias = 'user',
1740 default_value = '1 hour 30 minutes',
1741 validator = is_valid
1742 )
1743
1748
1749 gmCfgWidgets.configure_string_option (
1750 message = _(
1751 'When a patient is activated GNUmed checks the\n'
1752 'age of the most recent encounter.\n'
1753 '\n'
1754 'If that encounter is older than this age\n'
1755 'GNUmed will always start a new encounter.\n'
1756 '\n'
1757 '(If it is very recent the existing encounter\n'
1758 ' is continued, or else GNUmed will ask you.)\n'
1759 ),
1760 option = 'encounter.maximum_ttl',
1761 bias = 'user',
1762 default_value = '6 hours',
1763 validator = is_valid
1764 )
1765
1774
1775 gmCfgWidgets.configure_string_option (
1776 message = _(
1777 'At any time there can only be one open (ongoing)\n'
1778 'episode for each health issue.\n'
1779 '\n'
1780 'When you try to open (add data to) an episode on a health\n'
1781 'issue GNUmed will check for an existing open episode on\n'
1782 'that issue. If there is any it will check the age of that\n'
1783 'episode. The episode is closed if it has been dormant (no\n'
1784 'data added, that is) for the period of time (in days) you\n'
1785 'set here.\n'
1786 '\n'
1787 "If the existing episode hasn't been dormant long enough\n"
1788 'GNUmed will consult you what to do.\n'
1789 '\n'
1790 'Enter maximum episode dormancy in DAYS:'
1791 ),
1792 option = 'episode.ttl',
1793 bias = 'user',
1794 default_value = 60,
1795 validator = is_valid
1796 )
1797
1828
1843
1868
1880
1881 gmCfgWidgets.configure_string_option (
1882 message = _(
1883 'GNUmed can check for new releases being available. To do\n'
1884 'so it needs to load version information from an URL.\n'
1885 '\n'
1886 'The default URL is:\n'
1887 '\n'
1888 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1889 '\n'
1890 'but you can configure any other URL locally. Note\n'
1891 'that you must enter the location as a valid URL.\n'
1892 'Depending on the URL the client will need online\n'
1893 'access when checking for updates.'
1894 ),
1895 option = u'horstspace.update.url',
1896 bias = u'workplace',
1897 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1898 validator = is_valid
1899 )
1900
1918
1935
1952
1963
1964 gmCfgWidgets.configure_string_option (
1965 message = _(
1966 'GNUmed can show the document review dialog after\n'
1967 'calling the appropriate viewer for that document.\n'
1968 '\n'
1969 'Select the conditions under which you want\n'
1970 'GNUmed to do so:\n'
1971 '\n'
1972 ' 0: never display the review dialog\n'
1973 ' 1: always display the dialog\n'
1974 ' 2: only if there is no previous review by me\n'
1975 ' 3: only if there is no previous review at all\n'
1976 ' 4: only if there is no review by the responsible reviewer\n'
1977 '\n'
1978 'Note that if a viewer is configured to not block\n'
1979 'GNUmed during document display the review dialog\n'
1980 'will actually appear in parallel to the viewer.'
1981 ),
1982 option = u'horstspace.document_viewer.review_after_display',
1983 bias = u'user',
1984 default_value = 3,
1985 validator = is_valid
1986 )
1987
1989
1990
1991 master_data_lists = [
1992 'adr',
1993 'drugs',
1994 'codes',
1995 'communication_channel_types',
1996 'substances_in_brands',
1997 'substances',
1998 'labs',
1999 'form_templates',
2000 'doc_types',
2001 'enc_types',
2002 'text_expansions',
2003 'meta_test_types',
2004 'orgs',
2005 'patient_tags',
2006 'provinces',
2007 'db_translations',
2008 'test_types',
2009 'vacc_indications',
2010 'vaccines',
2011 'workplaces'
2012 ]
2013
2014 master_data_list_names = {
2015 'adr': _('Addresses (likely slow)'),
2016 'drugs': _('Branded drugs (as marketed)'),
2017 'codes': _('Codes and their respective terms'),
2018 'communication_channel_types': _('Communication channel types'),
2019 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2020 'labs': _('Diagnostic organizations (path labs, ...)'),
2021 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2022 'doc_types': _('Document types'),
2023 'enc_types': _('Encounter types'),
2024 'text_expansions': _('Keyword based text expansion macros'),
2025 'meta_test_types': _('Meta test/measurement types'),
2026 'orgs': _('Organizations with their units, addresses, and comm channels'),
2027 'patient_tags': _('Patient tags'),
2028 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2029 'db_translations': _('String translations in the database'),
2030 'test_types': _('Test/measurement types'),
2031 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2032 'vaccines': _('Vaccines'),
2033 'workplaces': _('Workplace profiles (which plugins to load)'),
2034 'substances': _('Consumable substances')
2035 }
2036
2037 map_list2handler = {
2038 'form_templates': gmFormWidgets.manage_form_templates,
2039 'doc_types': gmDocumentWidgets.manage_document_types,
2040 'text_expansions': gmProviderInboxWidgets.configure_keyword_text_expansion,
2041 'db_translations': gmI18nWidgets.manage_translations,
2042 'codes': gmCodingWidgets.browse_coded_terms,
2043 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2044 'provinces': gmAddressWidgets.manage_provinces,
2045 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins,
2046 'drugs': gmMedicationWidgets.manage_branded_drugs,
2047 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2048 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2049 'test_types': gmMeasurementWidgets.manage_measurement_types,
2050 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2051 'vaccines': gmVaccWidgets.manage_vaccines,
2052 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2053 'orgs': gmOrganizationWidgets.manage_orgs,
2054 'adr': gmAddressWidgets.manage_addresses,
2055 'substances': gmMedicationWidgets.manage_consumable_substances,
2056 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2057 'communication_channel_types': gmContactWidgets.manage_comm_channel_types
2058 }
2059
2060
2061 def edit(item):
2062 try: map_list2handler[item](parent = self)
2063 except KeyError: pass
2064 return False
2065
2066
2067 gmListWidgets.get_choices_from_list (
2068 parent = self,
2069 caption = _('Master data management'),
2070 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2071 data = master_data_lists,
2072 columns = [_('Select the list you want to manage:')],
2073 edit_callback = edit,
2074 single_selection = True,
2075 ignore_OK_button = True
2076 )
2077
2079
2080 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2081 if found:
2082 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2083 return
2084
2085 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2086 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking=False)
2087 return
2088
2089 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2090 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2091 if found:
2092 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2093 return
2094
2095 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2096
2098
2099 curr_pat = gmPerson.gmCurrentPatient()
2100
2101 arriba = gmArriba.cArriba()
2102 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2103 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2104 return
2105
2106
2107 if curr_pat is None:
2108 return
2109
2110 if arriba.pdf_result is None:
2111 return
2112
2113 doc = gmDocumentWidgets.save_file_as_new_document (
2114 parent = self,
2115 filename = arriba.pdf_result,
2116 document_type = _('risk assessment')
2117 )
2118
2119 try: os.remove(arriba.pdf_result)
2120 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2121
2122 if doc is None:
2123 return
2124
2125 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2126 doc.save()
2127
2128 try:
2129 open(arriba.xml_result).close()
2130 part = doc.add_part(file = arriba.xml_result)
2131 except StandardError:
2132 _log.exception('error accessing [%s]', arriba.xml_result)
2133 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2134
2135 if part is None:
2136 return
2137
2138 part['obj_comment'] = u'XML-Daten'
2139 part['filename'] = u'arriba-result.xml'
2140 part.save()
2141
2143
2144 dbcfg = gmCfg.cCfgSQL()
2145 cmd = dbcfg.get2 (
2146 option = u'external.tools.acs_risk_calculator_cmd',
2147 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2148 bias = 'user'
2149 )
2150
2151 if cmd is None:
2152 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2153 return
2154
2155 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
2156 try:
2157 subprocess.check_call (
2158 args = (cmd,),
2159 close_fds = True,
2160 cwd = cwd
2161 )
2162 except (OSError, ValueError, subprocess.CalledProcessError):
2163 _log.exception('there was a problem executing [%s]', cmd)
2164 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2165 return
2166
2167 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2168 for pdf in pdfs:
2169 try:
2170 open(pdf).close()
2171 except:
2172 _log.exception('error accessing [%s]', pdf)
2173 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2174 continue
2175
2176 doc = gmDocumentWidgets.save_file_as_new_document (
2177 parent = self,
2178 filename = pdf,
2179 document_type = u'risk assessment'
2180 )
2181
2182 try:
2183 os.remove(pdf)
2184 except StandardError:
2185 _log.exception('cannot remove [%s]', pdf)
2186
2187 if doc is None:
2188 continue
2189 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2190 doc.save()
2191
2192 return
2193
2195 dlg = gmSnellen.cSnellenCfgDlg()
2196 if dlg.ShowModal() != wx.ID_OK:
2197 return
2198
2199 frame = gmSnellen.cSnellenChart (
2200 width = dlg.vals[0],
2201 height = dlg.vals[1],
2202 alpha = dlg.vals[2],
2203 mirr = dlg.vals[3],
2204 parent = None
2205 )
2206 frame.CentreOnScreen(wx.BOTH)
2207
2208
2209 frame.Show(True)
2210
2211
2214
2217
2220
2221
2222
2226
2227
2228
2230 wx.CallAfter(self.__save_screenshot)
2231 evt.Skip()
2232
2234
2235 time.sleep(0.5)
2236
2237 rect = self.GetRect()
2238
2239
2240 if sys.platform == 'linux2':
2241 client_x, client_y = self.ClientToScreen((0, 0))
2242 border_width = client_x - rect.x
2243 title_bar_height = client_y - rect.y
2244
2245 if self.GetMenuBar():
2246 title_bar_height /= 2
2247 rect.width += (border_width * 2)
2248 rect.height += title_bar_height + border_width
2249
2250 wdc = wx.ScreenDC()
2251 mdc = wx.MemoryDC()
2252 img = wx.EmptyBitmap(rect.width, rect.height)
2253 mdc.SelectObject(img)
2254 mdc.Blit (
2255 0, 0,
2256 rect.width, rect.height,
2257 wdc,
2258 rect.x, rect.y
2259 )
2260
2261
2262 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2263 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2264 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2265
2267
2268 raise ValueError('raised ValueError to test exception handling')
2269
2271 import wx.lib.inspection
2272 wx.lib.inspection.InspectionTool().Show()
2273
2276
2279
2282
2285
2292
2296
2299
2306
2311
2313 name = os.path.basename(gmLog2._logfile_name)
2314 name, ext = os.path.splitext(name)
2315 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2316 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2317
2318 dlg = wx.FileDialog (
2319 parent = self,
2320 message = _("Save current log as..."),
2321 defaultDir = new_path,
2322 defaultFile = new_name,
2323 wildcard = "%s (*.log)|*.log" % _("log files"),
2324 style = wx.SAVE
2325 )
2326 choice = dlg.ShowModal()
2327 new_name = dlg.GetPath()
2328 dlg.Destroy()
2329 if choice != wx.ID_OK:
2330 return True
2331
2332 _log.warning('syncing log file for backup to [%s]', new_name)
2333 gmLog2.flush()
2334 shutil.copy2(gmLog2._logfile_name, new_name)
2335 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2336
2339
2340
2341
2343 """This is the wx.EVT_CLOSE handler.
2344
2345 - framework still functional
2346 """
2347 _log.debug('gmTopLevelFrame.OnClose() start')
2348 self._clean_exit()
2349 self.Destroy()
2350 _log.debug('gmTopLevelFrame.OnClose() end')
2351 return True
2352
2358
2363
2371
2378
2385
2392
2402
2410
2418
2426
2434
2443
2452
2460
2477
2480
2483
2485
2486 pat = gmPerson.gmCurrentPatient()
2487 if not pat.connected:
2488 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2489 return False
2490
2491 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2492
2493 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2494 gmTools.mkdir(aDefDir)
2495
2496 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2497 dlg = wx.FileDialog (
2498 parent = self,
2499 message = _("Save patient's EMR journal as..."),
2500 defaultDir = aDefDir,
2501 defaultFile = fname,
2502 wildcard = aWildcard,
2503 style = wx.SAVE
2504 )
2505 choice = dlg.ShowModal()
2506 fname = dlg.GetPath()
2507 dlg.Destroy()
2508 if choice != wx.ID_OK:
2509 return True
2510
2511 _log.debug('exporting EMR journal to [%s]' % fname)
2512
2513 exporter = gmPatientExporter.cEMRJournalExporter()
2514
2515 wx.BeginBusyCursor()
2516 try:
2517 fname = exporter.export_to_file(filename = fname)
2518 except:
2519 wx.EndBusyCursor()
2520 gmGuiHelpers.gm_show_error (
2521 _('Error exporting patient EMR as chronological journal.'),
2522 _('EMR journal export')
2523 )
2524 raise
2525 wx.EndBusyCursor()
2526
2527 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2528
2529 return True
2530
2537
2539 curr_pat = gmPerson.gmCurrentPatient()
2540 if not curr_pat.connected:
2541 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2542 return
2543
2544 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2545 if tag is None:
2546 return
2547
2548 tag = curr_pat.add_tag(tag['pk_tag_image'])
2549 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2550 comment = wx.GetTextFromUser (
2551 message = msg,
2552 caption = _('Editing tag comment'),
2553 default_value = gmTools.coalesce(tag['comment'], u''),
2554 parent = self
2555 )
2556
2557 if comment == u'':
2558 return
2559
2560 if comment.strip() == tag['comment']:
2561 return
2562
2563 if comment == u' ':
2564 tag['comment'] = None
2565 else:
2566 tag['comment'] = comment.strip()
2567
2568 tag.save()
2569
2579
2581 curr_pat = gmPerson.gmCurrentPatient()
2582 if not curr_pat.connected:
2583 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2584 return False
2585
2586 enc = 'cp850'
2587 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2588 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2589 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2590
2593
2601
2609
2612
2619
2623
2626
2627
2628
2629
2632
2635
2640
2642 """Cleanup helper.
2643
2644 - should ALWAYS be called when this program is
2645 to be terminated
2646 - ANY code that should be executed before a
2647 regular shutdown should go in here
2648 - framework still functional
2649 """
2650 _log.debug('gmTopLevelFrame._clean_exit() start')
2651
2652
2653 listener = gmBackendListener.gmBackendListener()
2654 try:
2655 listener.shutdown()
2656 except:
2657 _log.exception('cannot stop backend notifications listener thread')
2658
2659
2660 if _scripting_listener is not None:
2661 try:
2662 _scripting_listener.shutdown()
2663 except:
2664 _log.exception('cannot stop scripting listener thread')
2665
2666
2667 self.clock_update_timer.Stop()
2668 gmTimer.shutdown()
2669 gmPhraseWheel.shutdown()
2670
2671
2672 for call_back in self.__pre_exit_callbacks:
2673 try:
2674 call_back()
2675 except:
2676 print "*** pre-exit callback failed ***"
2677 print call_back
2678 _log.exception('callback [%s] failed', call_back)
2679
2680
2681 gmDispatcher.send(u'application_closing')
2682
2683
2684 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2685
2686
2687 curr_width, curr_height = self.GetClientSizeTuple()
2688 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2689 dbcfg = gmCfg.cCfgSQL()
2690 dbcfg.set (
2691 option = 'main.window.width',
2692 value = curr_width,
2693 workplace = gmSurgery.gmCurrentPractice().active_workplace
2694 )
2695 dbcfg.set (
2696 option = 'main.window.height',
2697 value = curr_height,
2698 workplace = gmSurgery.gmCurrentPractice().active_workplace
2699 )
2700
2701 if _cfg.get(option = 'debug'):
2702 print '---=== GNUmed shutdown ===---'
2703 try:
2704 print _('You have to manually close this window to finalize shutting down GNUmed.')
2705 print _('This is so that you can inspect the console output at your leisure.')
2706 except UnicodeEncodeError:
2707 print 'You have to manually close this window to finalize shutting down GNUmed.'
2708 print 'This is so that you can inspect the console output at your leisure.'
2709 print '---=== GNUmed shutdown ===---'
2710
2711
2712 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2713
2714
2715 import threading
2716 _log.debug("%s active threads", threading.activeCount())
2717 for t in threading.enumerate():
2718 _log.debug('thread %s', t)
2719
2720 _log.debug('gmTopLevelFrame._clean_exit() end')
2721
2722
2723
2725
2726 if _cfg.get(option = 'slave'):
2727 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2728 _cfg.get(option = 'slave personality'),
2729 _cfg.get(option = 'xml-rpc port')
2730 )
2731 else:
2732 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2733
2735 """Update title of main window based on template.
2736
2737 This gives nice tooltips on iconified GNUmed instances.
2738
2739 User research indicates that in the title bar people want
2740 the date of birth, not the age, so please stick to this
2741 convention.
2742 """
2743 args = {}
2744
2745 pat = gmPerson.gmCurrentPatient()
2746 if pat.connected:
2747 args['pat'] = u'%s %s %s (%s) #%d' % (
2748 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2749 pat['firstnames'],
2750 pat['lastnames'],
2751 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2752 pat['pk_identity']
2753 )
2754 else:
2755 args['pat'] = _('no patient')
2756
2757 args['prov'] = u'%s%s.%s' % (
2758 gmTools.coalesce(_provider['title'], u'', u'%s '),
2759 _provider['firstnames'][:1],
2760 _provider['lastnames']
2761 )
2762
2763 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2764
2765 self.SetTitle(self.__title_template % args)
2766
2767
2769 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2770 sb.SetStatusWidths([-1, 225])
2771
2772 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2773 self._cb_update_clock()
2774
2775 self.clock_update_timer.Start(milliseconds = 1000)
2776
2778 """Displays date and local time in the second slot of the status bar"""
2779 t = time.localtime(time.time())
2780 st = time.strftime('%c', t).decode(gmI18N.get_encoding(), 'replace')
2781 self.SetStatusText(st, 1)
2782
2784 """Lock GNUmed client against unauthorized access"""
2785
2786
2787
2788 return
2789
2791 """Unlock the main notebook widgets
2792 As long as we are not logged into the database backend,
2793 all pages but the 'login' page of the main notebook widget
2794 are locked; i.e. not accessible by the user
2795 """
2796
2797
2798
2799
2800
2801 return
2802
2804 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2805
2807
2809
2810 self.__starting_up = True
2811
2812 gmExceptionHandlingWidgets.install_wx_exception_handler()
2813 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2814
2815
2816
2817
2818 self.SetAppName(u'gnumed')
2819 self.SetVendorName(u'The GNUmed Development Community.')
2820 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2821 paths.init_paths(wx = wx, app_name = u'gnumed')
2822
2823 if not self.__setup_prefs_file():
2824 return False
2825
2826 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2827
2828 self.__guibroker = gmGuiBroker.GuiBroker()
2829 self.__setup_platform()
2830
2831 if not self.__establish_backend_connection():
2832 return False
2833
2834 if not _cfg.get(option = 'skip-update-check'):
2835 self.__check_for_updates()
2836
2837 if _cfg.get(option = 'slave'):
2838 if not self.__setup_scripting_listener():
2839 return False
2840
2841
2842 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2843 frame.CentreOnScreen(wx.BOTH)
2844 self.SetTopWindow(frame)
2845 frame.Show(True)
2846
2847 if _cfg.get(option = 'debug'):
2848 self.RedirectStdio()
2849 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2850
2851
2852 print '---=== GNUmed startup ===---'
2853 print _('redirecting STDOUT/STDERR to this log window')
2854 print '---=== GNUmed startup ===---'
2855
2856 self.__setup_user_activity_timer()
2857 self.__register_events()
2858
2859 wx.CallAfter(self._do_after_init)
2860
2861 return True
2862
2864 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2865
2866 - after destroying all application windows and controls
2867 - before wx.Windows internal cleanup
2868 """
2869 _log.debug('gmApp.OnExit() start')
2870
2871 self.__shutdown_user_activity_timer()
2872
2873 if _cfg.get(option = 'debug'):
2874 self.RestoreStdio()
2875 sys.stdin = sys.__stdin__
2876 sys.stdout = sys.__stdout__
2877 sys.stderr = sys.__stderr__
2878
2879 _log.debug('gmApp.OnExit() end')
2880
2882 wx.Bell()
2883 wx.Bell()
2884 wx.Bell()
2885 _log.warning('unhandled event detected: QUERY_END_SESSION')
2886 _log.info('we should be saving ourselves from here')
2887 gmLog2.flush()
2888 print "unhandled event detected: QUERY_END_SESSION"
2889
2891 wx.Bell()
2892 wx.Bell()
2893 wx.Bell()
2894 _log.warning('unhandled event detected: END_SESSION')
2895 gmLog2.flush()
2896 print "unhandled event detected: END_SESSION"
2897
2908
2910 self.user_activity_detected = True
2911 evt.Skip()
2912
2914
2915 if self.user_activity_detected:
2916 self.elapsed_inactivity_slices = 0
2917 self.user_activity_detected = False
2918 self.elapsed_inactivity_slices += 1
2919 else:
2920 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2921
2922 pass
2923
2924 self.user_activity_timer.Start(oneShot = True)
2925
2926
2927
2929 try:
2930 kwargs['originated_in_database']
2931 print '==> got notification from database "%s":' % kwargs['signal']
2932 except KeyError:
2933 print '==> received signal from client: "%s"' % kwargs['signal']
2934
2935 del kwargs['signal']
2936 for key in kwargs.keys():
2937 print ' [%s]: %s' % (key, kwargs[key])
2938
2940 print "wx.lib.pubsub message:"
2941 print msg.topic
2942 print msg.data
2943
2949
2951 self.user_activity_detected = True
2952 self.elapsed_inactivity_slices = 0
2953
2954 self.max_user_inactivity_slices = 15
2955 self.user_activity_timer = gmTimer.cTimer (
2956 callback = self._on_user_activity_timer_expired,
2957 delay = 2000
2958 )
2959 self.user_activity_timer.Start(oneShot=True)
2960
2962 try:
2963 self.user_activity_timer.Stop()
2964 del self.user_activity_timer
2965 except:
2966 pass
2967
2969 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2970 wx.EVT_END_SESSION(self, self._on_end_session)
2971
2972
2973
2974
2975
2976 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2977
2978 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2979 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2980
2981 if _cfg.get(option = 'debug'):
2982 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
2983 _log.debug('connected old signal monitor')
2984 wx.lib.pubsub.Publisher().subscribe(listener = self._signal_debugging_monitor_pubsub)
2985 _log.debug('connected wx.lib.pubsub based signal monitor for all topics')
2986
2987
2988
2989
2990
2991
3007
3009 """Handle all the database related tasks necessary for startup."""
3010
3011
3012 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
3013
3014 from Gnumed.wxpython import gmAuthWidgets
3015 connected = gmAuthWidgets.connect_to_database (
3016 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
3017 require_version = not override
3018 )
3019 if not connected:
3020 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
3021 return False
3022
3023
3024 try:
3025 global _provider
3026 _provider = gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
3027 except ValueError:
3028 account = gmPG2.get_current_user()
3029 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
3030 msg = _(
3031 'The database account [%s] cannot be used as a\n'
3032 'staff member login for GNUmed. There was an\n'
3033 'error retrieving staff details for it.\n\n'
3034 'Please ask your administrator for help.\n'
3035 ) % account
3036 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
3037 return False
3038
3039
3040 tmp = '%s%s %s (%s = %s)' % (
3041 gmTools.coalesce(_provider['title'], ''),
3042 _provider['firstnames'],
3043 _provider['lastnames'],
3044 _provider['short_alias'],
3045 _provider['db_user']
3046 )
3047 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
3048
3049
3050 surgery = gmSurgery.gmCurrentPractice()
3051 msg = surgery.db_logon_banner
3052 if msg.strip() != u'':
3053
3054 login = gmPG2.get_default_login()
3055 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
3056 login.database,
3057 gmTools.coalesce(login.host, u'localhost')
3058 ))
3059 msg = auth + msg + u'\n\n'
3060
3061 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3062 None,
3063
3064 -1,
3065 caption = _('Verifying database'),
3066 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3067 button_defs = [
3068 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3069 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3070 ]
3071 )
3072 go_on = dlg.ShowModal()
3073 dlg.Destroy()
3074 if go_on != wx.ID_YES:
3075 _log.info('user decided to not connect to this database')
3076 return False
3077
3078
3079 self.__check_db_lang()
3080
3081 return True
3082
3084 """Setup access to a config file for storing preferences."""
3085
3086 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3087
3088 candidates = []
3089 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3090 if explicit_file is not None:
3091 candidates.append(explicit_file)
3092
3093 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3094 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3095 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3096
3097 prefs_file = None
3098 for candidate in candidates:
3099 try:
3100 open(candidate, 'a+').close()
3101 prefs_file = candidate
3102 break
3103 except IOError:
3104 continue
3105
3106 if prefs_file is None:
3107 msg = _(
3108 'Cannot find configuration file in any of:\n'
3109 '\n'
3110 ' %s\n'
3111 'You may need to use the comand line option\n'
3112 '\n'
3113 ' --conf-file=<FILE>'
3114 ) % '\n '.join(candidates)
3115 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3116 return False
3117
3118 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3119 _log.info('user preferences file: %s', prefs_file)
3120
3121 return True
3122
3124
3125 from socket import error as SocketError
3126 from Gnumed.pycommon import gmScriptingListener
3127 from Gnumed.wxpython import gmMacro
3128
3129 slave_personality = gmTools.coalesce (
3130 _cfg.get (
3131 group = u'workplace',
3132 option = u'slave personality',
3133 source_order = [
3134 ('explicit', 'return'),
3135 ('workbase', 'return'),
3136 ('user', 'return'),
3137 ('system', 'return')
3138 ]
3139 ),
3140 u'gnumed-client'
3141 )
3142 _cfg.set_option(option = 'slave personality', value = slave_personality)
3143
3144
3145 port = int (
3146 gmTools.coalesce (
3147 _cfg.get (
3148 group = u'workplace',
3149 option = u'xml-rpc port',
3150 source_order = [
3151 ('explicit', 'return'),
3152 ('workbase', 'return'),
3153 ('user', 'return'),
3154 ('system', 'return')
3155 ]
3156 ),
3157 9999
3158 )
3159 )
3160 _cfg.set_option(option = 'xml-rpc port', value = port)
3161
3162 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3163 global _scripting_listener
3164 try:
3165 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3166 except SocketError, e:
3167 _log.exception('cannot start GNUmed XML-RPC server')
3168 gmGuiHelpers.gm_show_error (
3169 aMessage = (
3170 'Cannot start the GNUmed server:\n'
3171 '\n'
3172 ' [%s]'
3173 ) % e,
3174 aTitle = _('GNUmed startup')
3175 )
3176 return False
3177
3178 return True
3179
3200
3202 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3203 _log.warning("system locale is undefined (probably meaning 'C')")
3204 return True
3205
3206
3207 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3208 db_lang = rows[0]['lang']
3209
3210 if db_lang is None:
3211 _log.debug("database locale currently not set")
3212 msg = _(
3213 "There is no language selected in the database for user [%s].\n"
3214 "Your system language is currently set to [%s].\n\n"
3215 "Do you want to set the database language to '%s' ?\n\n"
3216 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3217 checkbox_msg = _('Remember to ignore missing language')
3218 else:
3219 _log.debug("current database locale: [%s]" % db_lang)
3220 msg = _(
3221 "The currently selected database language ('%s') does\n"
3222 "not match the current system language ('%s').\n"
3223 "\n"
3224 "Do you want to set the database language to '%s' ?\n"
3225 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3226 checkbox_msg = _('Remember to ignore language mismatch')
3227
3228
3229 if db_lang == gmI18N.system_locale_level['full']:
3230 _log.debug('Database locale (%s) up to date.' % db_lang)
3231 return True
3232 if db_lang == gmI18N.system_locale_level['country']:
3233 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3234 return True
3235 if db_lang == gmI18N.system_locale_level['language']:
3236 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3237 return True
3238
3239 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3240
3241
3242 ignored_sys_lang = _cfg.get (
3243 group = u'backend',
3244 option = u'ignored mismatching system locale',
3245 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3246 )
3247
3248
3249 if gmI18N.system_locale == ignored_sys_lang:
3250 _log.info('configured to ignore system-to-database locale mismatch')
3251 return True
3252
3253
3254 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3255 None,
3256 -1,
3257 caption = _('Checking database language settings'),
3258 question = msg,
3259 button_defs = [
3260 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3261 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3262 ],
3263 show_checkbox = True,
3264 checkbox_msg = checkbox_msg,
3265 checkbox_tooltip = _(
3266 'Checking this will make GNUmed remember your decision\n'
3267 'until the system language is changed.\n'
3268 '\n'
3269 'You can also reactivate this inquiry by removing the\n'
3270 'corresponding "ignore" option from the configuration file\n'
3271 '\n'
3272 ' [%s]'
3273 ) % _cfg.get(option = 'user_preferences_file')
3274 )
3275 decision = dlg.ShowModal()
3276 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3277 dlg.Destroy()
3278
3279 if decision == wx.ID_NO:
3280 if not remember_ignoring_problem:
3281 return True
3282 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3283 gmCfg2.set_option_in_INI_file (
3284 filename = _cfg.get(option = 'user_preferences_file'),
3285 group = 'backend',
3286 option = 'ignored mismatching system locale',
3287 value = gmI18N.system_locale
3288 )
3289 return True
3290
3291
3292 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3293 if len(lang) > 0:
3294
3295
3296 rows, idx = gmPG2.run_rw_queries (
3297 link_obj = None,
3298 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3299 return_data = True
3300 )
3301 if rows[0][0]:
3302 _log.debug("Successfully set database language to [%s]." % lang)
3303 else:
3304 _log.error('Cannot set database language to [%s].' % lang)
3305 continue
3306 return True
3307
3308
3309 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3310 gmPG2.run_rw_queries(queries = [{
3311 'cmd': u'select i18n.force_curr_lang(%s)',
3312 'args': [gmI18N.system_locale_level['country']]
3313 }])
3314
3315 return True
3316
3318 try:
3319 kwargs['originated_in_database']
3320 print '==> got notification from database "%s":' % kwargs['signal']
3321 except KeyError:
3322 print '==> received signal from client: "%s"' % kwargs['signal']
3323
3324 del kwargs['signal']
3325 for key in kwargs.keys():
3326
3327 try: print ' [%s]: %s' % (key, kwargs[key])
3328 except: print 'cannot print signal information'
3329
3331
3332 try:
3333 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3334 print ' data: %s' % msg.data
3335 print msg
3336 except: print 'problem printing pubsub message information'
3337
3339
3340 if _cfg.get(option = 'debug'):
3341 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3342 _log.debug('gmDispatcher signal monitor activated')
3343 wx.lib.pubsub.Publisher().subscribe (
3344 listener = _signal_debugging_monitor_pubsub
3345
3346 )
3347 _log.debug('wx.lib.pubsub signal monitor activated')
3348
3349 wx.InitAllImageHandlers()
3350
3351
3352
3353 app = gmApp(redirect = False, clearSigInt = False)
3354 app.MainLoop()
3355
3356
3357
3358 if __name__ == '__main__':
3359
3360 from GNUmed.pycommon import gmI18N
3361 gmI18N.activate_locale()
3362 gmI18N.install_domain()
3363
3364 _log.info('Starting up as main module.')
3365 main()
3366
3367
3368