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 webbrowser, 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
52
53 from Gnumed.business import gmPerson, gmClinicalRecord, gmSurgery, gmEMRStructItems
54 from Gnumed.business import gmVaccination
55 from Gnumed.business import gmArriba
56
57 from Gnumed.exporters import gmPatientExporter
58
59 from Gnumed.wxpython import gmGuiHelpers, gmHorstSpace, gmEMRBrowser
60 from Gnumed.wxpython import gmDemographicsWidgets, gmEMRStructWidgets
61 from Gnumed.wxpython import gmPatSearchWidgets, gmAllergyWidgets, gmListWidgets
62 from Gnumed.wxpython import gmProviderInboxWidgets, gmCfgWidgets, gmExceptionHandlingWidgets
63 from Gnumed.wxpython import gmNarrativeWidgets, gmPhraseWheel, gmMedicationWidgets
64 from Gnumed.wxpython import gmStaffWidgets, gmDocumentWidgets, gmTimer, gmMeasurementWidgets
65 from Gnumed.wxpython import gmFormWidgets, gmSnellen
66 from Gnumed.wxpython import gmVaccWidgets
67 from Gnumed.wxpython import gmPersonContactWidgets
68 from Gnumed.wxpython import gmI18nWidgets
69 from Gnumed.wxpython import gmCodingWidgets
70 from Gnumed.wxpython import gmOrganizationWidgets
71 from Gnumed.wxpython import gmAuthWidgets
72 from Gnumed.wxpython import gmFamilyHistoryWidgets
73 from Gnumed.wxpython import gmDataPackWidgets
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 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
293 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
294
295 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
296
297
298 menu_cfg_ui = wx.Menu()
299
300
301 menu_cfg_doc = wx.Menu()
302
303 ID = wx.NewId()
304 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
305 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
306
307 ID = wx.NewId()
308 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
309 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
310
311 ID = wx.NewId()
312 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
313 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
314
315 item = menu_cfg_doc.Append(-1, _('Generate UUID'), _('Whether to generate UUIDs for new documents.'))
316 self.Bind(wx.EVT_MENU, self.__on_configure_generate_doc_uuid, item)
317
318 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
319
320
321 menu_cfg_update = wx.Menu()
322
323 ID = wx.NewId()
324 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
325 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
326
327 ID = wx.NewId()
328 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
329 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
330
331 ID = wx.NewId()
332 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
333 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
334
335 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
336
337
338 menu_cfg_pat_search = wx.Menu()
339
340 ID = wx.NewId()
341 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
342 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
343
344 ID = wx.NewId()
345 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
346 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
347
348 ID = wx.NewId()
349 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
350 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
351
352 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
353 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
354
355 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
356 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
357
358 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
359
360
361 menu_cfg_soap_editing = wx.Menu()
362
363 ID = wx.NewId()
364 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
365 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
366
367 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.'))
368 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item)
369
370 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
371
372 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
373
374
375 menu_cfg_ext_tools = wx.Menu()
376
377
378
379
380
381 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
382 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
383
384 ID = wx.NewId()
385 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
386 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
387
388 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
389 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
390
391 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
392 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
393
394 item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.'))
395 self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item)
396
397 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
398 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
399
400 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
401 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
402
403 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
404 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
405
406 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
407 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
408
409 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
410
411
412 menu_cfg_emr = wx.Menu()
413
414 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
415 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
416
417 item = menu_cfg_emr.Append(-1, _('Primary doctor'), _('Select the primary doctor to fall back to for patients without one.'))
418 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
419
420
421 menu_cfg_encounter = wx.Menu()
422
423 ID = wx.NewId()
424 menu_cfg_encounter.Append(ID, _('Edit before patient change'), _('Edit encounter details before change of patient.'))
425 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
426
427 ID = wx.NewId()
428 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
429 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
430
431 ID = wx.NewId()
432 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
433 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
434
435 ID = wx.NewId()
436 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
437 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
438
439 ID = wx.NewId()
440 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
441 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
442
443 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
444
445
446 menu_cfg_episode = wx.Menu()
447
448 ID = wx.NewId()
449 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
450 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
451
452 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
453 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
454 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
455
456
457 menu_master_data = wx.Menu()
458
459 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
460 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
461
462 item = menu_master_data.Append(-1, _('Install data packs'), _('Install reference data from data packs.'))
463 self.Bind(wx.EVT_MENU, self.__on_install_data_packs, item)
464
465 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
466 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
467
468 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
469 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
470
471 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
472 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
473
474 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
475
476
477 menu_users = wx.Menu()
478
479 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
480 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
481
482 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
483 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
484
485 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
486 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
487
488 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
489
490
491 menu_gnumed.AppendSeparator()
492
493 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
494 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
495
496 self.mainmenu.Append(menu_gnumed, '&GNUmed')
497
498
499 menu_person = wx.Menu()
500
501 ID_CREATE_PATIENT = wx.NewId()
502 menu_person.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
503 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
504
505 ID_LOAD_EXT_PAT = wx.NewId()
506 menu_person.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
507 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
508
509 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
510 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
511
512 ID_DEL_PAT = wx.NewId()
513 menu_person.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
514 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
515
516 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
517 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
518
519 menu_person.AppendSeparator()
520
521 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
522 menu_person.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
523 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
524
525
526 ID = wx.NewId()
527 menu_person.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
528 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
529
530 menu_person.AppendSeparator()
531
532 self.mainmenu.Append(menu_person, '&Person')
533 self.__gb['main.patientmenu'] = menu_person
534
535
536 menu_emr = wx.Menu()
537
538
539 menu_emr_show = wx.Menu()
540
541 item = menu_emr_show.Append(-1, _('Summary'), _('Show a high-level summary of the EMR.'))
542 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
543
544 menu_emr.AppendMenu(wx.NewId(), _('Show as ...'), menu_emr_show)
545 self.__gb['main.emr_showmenu'] = menu_emr_show
546
547
548 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
549 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
550
551 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
552 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
553
554
555 menu_emr_edit = wx.Menu()
556
557 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'))
558 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
559
560 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
561 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
562
563 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
564 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
565
566 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
567 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
568
569 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
570 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
571
572 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
573 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
574
575 item = menu_emr_edit.Append(-1, _('&Measurement(s)'), _('Add (a) measurement result(s) for the current patient.'))
576 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
577
578 item = menu_emr_edit.Append(-1, _('&Vaccination(s)'), _('Add (a) vaccination(s) for the current patient.'))
579 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
580
581 item = menu_emr_edit.Append(-1, _('&Family history (FHx)'), _('Manage family history.'))
582 self.Bind(wx.EVT_MENU, self.__on_manage_fhx, item)
583
584 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
585
586
587
588 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
589 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
590
591 item = menu_emr.Append(-1, _('&Encounters list'), _('List all encounters including empty ones.'))
592 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
593
594 menu_emr.AppendSeparator()
595
596
597 menu_emr_export = wx.Menu()
598
599 ID_EXPORT_EMR_ASCII = wx.NewId()
600 menu_emr_export.Append (
601 ID_EXPORT_EMR_ASCII,
602 _('Text document'),
603 _("Export the EMR of the active patient into a text file")
604 )
605 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
606
607 ID_EXPORT_EMR_JOURNAL = wx.NewId()
608 menu_emr_export.Append (
609 ID_EXPORT_EMR_JOURNAL,
610 _('Journal'),
611 _("Export the EMR of the active patient as a chronological journal into a text file")
612 )
613 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
614
615 ID_EXPORT_MEDISTAR = wx.NewId()
616 menu_emr_export.Append (
617 ID_EXPORT_MEDISTAR,
618 _('MEDISTAR import format'),
619 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
620 )
621 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
622
623 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
624
625 menu_emr.AppendSeparator()
626
627 self.mainmenu.Append(menu_emr, _("&EMR"))
628 self.__gb['main.emrmenu'] = menu_emr
629
630
631 menu_paperwork = wx.Menu()
632
633 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
634 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
635
636 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
637
638
639 self.menu_tools = wx.Menu()
640
641 ID_DICOM_VIEWER = wx.NewId()
642 viewer = _('no viewer installed')
643 if gmShellAPI.detect_external_binary(binary = 'ginkgocadx')[0]:
644 viewer = u'Ginkgo CADx'
645 elif os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
646 viewer = u'OsiriX'
647 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
648 viewer = u'Aeskulap'
649 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
650 viewer = u'AMIDE'
651 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
652 viewer = u'DicomScope'
653 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
654 viewer = u'(x)medcon'
655 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)
656 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
657 if viewer == _('no viewer installed'):
658 _log.info('neither of OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
659 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
660
661
662
663
664
665 ID = wx.NewId()
666 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
667 wx.EVT_MENU(self, ID, self.__on_snellen)
668
669 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
670 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
671
672 item = self.menu_tools.Append(-1, _('arriba'), _('arriba: cardiovascular risk assessment (%s).') % u'www.arriba-hausarzt.de')
673 self.Bind(wx.EVT_MENU, self.__on_arriba, item)
674
675 self.menu_tools.AppendSeparator()
676
677 self.mainmenu.Append(self.menu_tools, _("&Tools"))
678 self.__gb['main.toolsmenu'] = self.menu_tools
679
680
681 menu_knowledge = wx.Menu()
682
683
684 menu_drug_dbs = wx.Menu()
685
686 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
687 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
688
689
690
691
692
693
694 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
695
696 menu_id = wx.NewId()
697 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
698 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
699
700
701
702
703 ID_MEDICAL_LINKS = wx.NewId()
704 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
705 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
706
707 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
708 self.__gb['main.knowledgemenu'] = menu_knowledge
709
710
711 self.menu_office = wx.Menu()
712
713 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
714 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
715
716 self.menu_office.AppendSeparator()
717
718 self.mainmenu.Append(self.menu_office, _('&Office'))
719 self.__gb['main.officemenu'] = self.menu_office
720
721
722 help_menu = wx.Menu()
723
724 ID = wx.NewId()
725 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
726 wx.EVT_MENU(self, ID, self.__on_display_wiki)
727
728 ID = wx.NewId()
729 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
730 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
731
732 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
733 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
734
735 menu_debugging = wx.Menu()
736
737 ID_SCREENSHOT = wx.NewId()
738 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
739 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
740
741 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
742 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
743
744 ID = wx.NewId()
745 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
746 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
747
748 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
749 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
750
751 ID = wx.NewId()
752 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
753 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
754
755 ID_UNBLOCK = wx.NewId()
756 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
757 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
758
759 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
760 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
761
762
763
764
765 if _cfg.get(option = 'debug'):
766 ID_TOGGLE_PAT_LOCK = wx.NewId()
767 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient'), _('Lock/unlock patient - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
768 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
769
770 ID_TEST_EXCEPTION = wx.NewId()
771 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
772 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
773
774 ID = wx.NewId()
775 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
776 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
777 try:
778 import wx.lib.inspection
779 except ImportError:
780 menu_debugging.Enable(id = ID, enable = False)
781
782 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
783
784 help_menu.AppendSeparator()
785
786 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
787 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
788
789 ID_CONTRIBUTORS = wx.NewId()
790 help_menu.Append(ID_CONTRIBUTORS, _('GNUmed contributors'), _('show GNUmed contributors'))
791 wx.EVT_MENU(self, ID_CONTRIBUTORS, self.__on_show_contributors)
792
793 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
794 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
795
796 help_menu.AppendSeparator()
797
798 self.mainmenu.Append(help_menu, _("&Help"))
799
800 self.__gb['main.helpmenu'] = help_menu
801
802
803 self.SetMenuBar(self.mainmenu)
804
807
808
809
811 """register events we want to react to"""
812
813 wx.EVT_CLOSE(self, self.OnClose)
814 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
815 wx.EVT_END_SESSION(self, self._on_end_session)
816
817 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
818 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
819 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
820 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
821 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
822 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
823 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
824 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
825
826 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
827
828 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
829
830 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
831
832 _log.debug('registering plugin with menu system')
833 _log.debug(' generic name: %s', plugin_name)
834 _log.debug(' class name: %s', class_name)
835 _log.debug(' specific menu: %s', menu_name)
836 _log.debug(' menu item: %s', menu_item_name)
837
838
839 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
840 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
841 self.menu_id2plugin[item.Id] = class_name
842
843
844 if menu_name is not None:
845 menu = self.__gb['main.%smenu' % menu_name]
846 item = menu.Append(-1, menu_item_name, menu_help_string)
847 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
848 self.menu_id2plugin[item.Id] = class_name
849
850 return True
851
853 gmDispatcher.send (
854 signal = u'display_widget',
855 name = self.menu_id2plugin[evt.Id]
856 )
857
859 wx.Bell()
860 wx.Bell()
861 wx.Bell()
862 _log.warning('unhandled event detected: QUERY_END_SESSION')
863 _log.info('we should be saving ourselves from here')
864 gmLog2.flush()
865 print "unhandled event detected: QUERY_END_SESSION"
866
868 wx.Bell()
869 wx.Bell()
870 wx.Bell()
871 _log.warning('unhandled event detected: END_SESSION')
872 gmLog2.flush()
873 print "unhandled event detected: END_SESSION"
874
876 if not callable(callback):
877 raise TypeError(u'callback [%s] not callable' % callback)
878
879 self.__pre_exit_callbacks.append(callback)
880
881 - def _on_set_statustext_pubsub(self, context=None):
882 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
883 wx.CallAfter(self.SetStatusText, msg)
884
885 try:
886 if context.data['beep']:
887 wx.Bell()
888 except KeyError:
889 pass
890
891 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
892
893 if msg is None:
894 msg = _('programmer forgot to specify status message')
895
896 if loglevel is not None:
897 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
898
899 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
900 wx.CallAfter(self.SetStatusText, msg)
901
902 if beep:
903 wx.Bell()
904
906 wx.CallAfter(self.__on_db_maintenance_warning)
907
909
910 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
911 wx.Bell()
912 if not wx.GetApp().IsActive():
913 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
914
915 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
916
917 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
918 None,
919 -1,
920 caption = _('Database shutdown warning'),
921 question = _(
922 'The database will be shut down for maintenance\n'
923 'in a few minutes.\n'
924 '\n'
925 'In order to not suffer any loss of data you\n'
926 'will need to save your current work and log\n'
927 'out of this GNUmed client.\n'
928 ),
929 button_defs = [
930 {
931 u'label': _('Close now'),
932 u'tooltip': _('Close this GNUmed client immediately.'),
933 u'default': False
934 },
935 {
936 u'label': _('Finish work'),
937 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
938 u'default': True
939 }
940 ]
941 )
942 decision = dlg.ShowModal()
943 if decision == wx.ID_YES:
944 top_win = wx.GetApp().GetTopWindow()
945 wx.CallAfter(top_win.Close)
946
948 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
949
951
952 if not wx.GetApp().IsActive():
953 if urgent:
954 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
955 else:
956 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
957
958 if msg is not None:
959 self.SetStatusText(msg)
960
961 if urgent:
962 wx.Bell()
963
964 gmHooks.run_hook_script(hook = u'request_user_attention')
965
967 wx.CallAfter(self.__on_pat_name_changed)
968
970 self.__update_window_title()
971
973 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
974
976 self.__update_window_title()
977 try:
978 gmHooks.run_hook_script(hook = u'post_patient_activation')
979 except:
980 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
981 raise
982
984 return self.__sanity_check_encounter()
985
1047
1048
1049
1052
1060
1061
1062
1077
1100
1102 from Gnumed.wxpython import gmAbout
1103 contribs = gmAbout.cContributorsDlg (
1104 parent = self,
1105 id = -1,
1106 title = _('GNUmed contributors'),
1107 size = wx.Size(400,600),
1108 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1109 )
1110 contribs.ShowModal()
1111 del contribs
1112 del gmAbout
1113
1114
1115
1117 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1118 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1119 self.Close(True)
1120 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1121
1124
1126 send = gmGuiHelpers.gm_show_question (
1127 _('This will send a notification about database downtime\n'
1128 'to all GNUmed clients connected to your database.\n'
1129 '\n'
1130 'Do you want to send the notification ?\n'
1131 ),
1132 _('Announcing database maintenance downtime')
1133 )
1134 if not send:
1135 return
1136 gmPG2.send_maintenance_notification()
1137
1138
1141
1142
1143
1156
1157 gmCfgWidgets.configure_string_option (
1158 message = _(
1159 'Some network installations cannot cope with loading\n'
1160 'documents of arbitrary size in one piece from the\n'
1161 'database (mainly observed on older Windows versions)\n.'
1162 '\n'
1163 'Under such circumstances documents need to be retrieved\n'
1164 'in chunks and reassembled on the client.\n'
1165 '\n'
1166 'Here you can set the size (in Bytes) above which\n'
1167 'GNUmed will retrieve documents in chunks. Setting this\n'
1168 'value to 0 will disable the chunking protocol.'
1169 ),
1170 option = 'horstspace.blob_export_chunk_size',
1171 bias = 'workplace',
1172 default_value = 1024 * 1024,
1173 validator = is_valid
1174 )
1175
1176
1177
1245
1249
1250
1251
1260
1261 gmCfgWidgets.configure_string_option (
1262 message = _(
1263 'When GNUmed cannot find an OpenOffice server it\n'
1264 'will try to start one. OpenOffice, however, needs\n'
1265 'some time to fully start up.\n'
1266 '\n'
1267 'Here you can set the time for GNUmed to wait for OOo.\n'
1268 ),
1269 option = 'external.ooo.startup_settle_time',
1270 bias = 'workplace',
1271 default_value = 2.0,
1272 validator = is_valid
1273 )
1274
1277
1292
1293 gmCfgWidgets.configure_string_option (
1294 message = _(
1295 'GNUmed will use this URL to access a website which lets\n'
1296 'you report an adverse drug reaction (ADR).\n'
1297 '\n'
1298 'If you leave this empty it will fall back\n'
1299 'to an URL for reporting ADRs in Germany.'
1300 ),
1301 option = 'external.urls.report_ADR',
1302 bias = 'user',
1303 default_value = german_default,
1304 validator = is_valid
1305 )
1306
1320
1321 gmCfgWidgets.configure_string_option (
1322 message = _(
1323 'GNUmed will use this URL to access a website which lets\n'
1324 'you report an adverse vaccination reaction (vADR).\n'
1325 '\n'
1326 'If you set it to a specific address that URL must be\n'
1327 'accessible now. If you leave it empty it will fall back\n'
1328 'to the URL for reporting other adverse drug reactions.'
1329 ),
1330 option = 'external.urls.report_vaccine_ADR',
1331 bias = 'user',
1332 default_value = german_default,
1333 validator = is_valid
1334 )
1335
1349
1350 gmCfgWidgets.configure_string_option (
1351 message = _(
1352 'GNUmed will use this URL to access an encyclopedia of\n'
1353 'measurement/lab methods from within the measurments grid.\n'
1354 '\n'
1355 'You can leave this empty but to set it to a specific\n'
1356 'address the URL must be accessible now.'
1357 ),
1358 option = 'external.urls.measurements_encyclopedia',
1359 bias = 'user',
1360 default_value = german_default,
1361 validator = is_valid
1362 )
1363
1377
1378 gmCfgWidgets.configure_string_option (
1379 message = _(
1380 'GNUmed will use this URL to access a page showing\n'
1381 'vaccination schedules.\n'
1382 '\n'
1383 'You can leave this empty but to set it to a specific\n'
1384 'address the URL must be accessible now.'
1385 ),
1386 option = 'external.urls.vaccination_plans',
1387 bias = 'user',
1388 default_value = german_default,
1389 validator = is_valid
1390 )
1391
1404
1405 gmCfgWidgets.configure_string_option (
1406 message = _(
1407 'Enter the shell command with which to start the\n'
1408 'the ACS risk assessment calculator.\n'
1409 '\n'
1410 'GNUmed will try to verify the path which may,\n'
1411 'however, fail if you are using an emulator such\n'
1412 'as Wine. Nevertheless, starting the calculator\n'
1413 'will work as long as the shell command is correct\n'
1414 'despite the failing test.'
1415 ),
1416 option = 'external.tools.acs_risk_calculator_cmd',
1417 bias = 'user',
1418 validator = is_valid
1419 )
1420
1423
1436
1437 gmCfgWidgets.configure_string_option (
1438 message = _(
1439 'Enter the shell command with which to start\n'
1440 'the FreeDiams drug database frontend.\n'
1441 '\n'
1442 'GNUmed will try to verify that path.'
1443 ),
1444 option = 'external.tools.freediams_cmd',
1445 bias = 'workplace',
1446 default_value = None,
1447 validator = is_valid
1448 )
1449
1462
1463 gmCfgWidgets.configure_string_option (
1464 message = _(
1465 'Enter the shell command with which to start the\n'
1466 'the IFAP drug database.\n'
1467 '\n'
1468 'GNUmed will try to verify the path which may,\n'
1469 'however, fail if you are using an emulator such\n'
1470 'as Wine. Nevertheless, starting IFAP will work\n'
1471 'as long as the shell command is correct despite\n'
1472 'the failing test.'
1473 ),
1474 option = 'external.ifap-win.shell_command',
1475 bias = 'workplace',
1476 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1477 validator = is_valid
1478 )
1479
1480
1481
1530
1531
1532
1549
1552
1555
1560
1561 gmCfgWidgets.configure_string_option (
1562 message = _(
1563 'When a patient is activated GNUmed checks the\n'
1564 "proximity of the patient's birthday.\n"
1565 '\n'
1566 'If the birthday falls within the range of\n'
1567 ' "today %s <the interval you set here>"\n'
1568 'GNUmed will remind you of the recent or\n'
1569 'imminent anniversary.'
1570 ) % u'\u2213',
1571 option = u'patient_search.dob_warn_interval',
1572 bias = 'user',
1573 default_value = '1 week',
1574 validator = is_valid
1575 )
1576
1578
1579 gmCfgWidgets.configure_boolean_option (
1580 parent = self,
1581 question = _(
1582 'When adding progress notes do you want to\n'
1583 'allow opening several unassociated, new\n'
1584 'episodes for a patient at once ?\n'
1585 '\n'
1586 'This can be particularly helpful when entering\n'
1587 'progress notes on entirely new patients presenting\n'
1588 'with a multitude of problems on their first visit.'
1589 ),
1590 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1591 button_tooltips = [
1592 _('Yes, allow for multiple new episodes concurrently.'),
1593 _('No, only allow editing one new episode at a time.')
1594 ]
1595 )
1596
1598
1599 gmCfgWidgets.configure_boolean_option (
1600 parent = self,
1601 question = _(
1602 'When activating a patient, do you want GNUmed to\n'
1603 'auto-open editors for all active problems that were\n'
1604 'touched upon during the current and the most recent\n'
1605 'encounter ?'
1606 ),
1607 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1608 button_tooltips = [
1609 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1610 _('No, only auto-open one editor for a new, unassociated problem.')
1611 ]
1612 )
1613
1659
1660
1661
1664
1667
1681
1683 gmCfgWidgets.configure_boolean_option (
1684 parent = self,
1685 question = _(
1686 'Do you want GNUmed to show the encounter\n'
1687 'details editor when changing the active patient ?'
1688 ),
1689 option = 'encounter.show_editor_before_patient_change',
1690 button_tooltips = [
1691 _('Yes, show the encounter editor if it seems appropriate.'),
1692 _('No, never show the encounter editor even if it would seem useful.')
1693 ]
1694 )
1695
1700
1701 gmCfgWidgets.configure_string_option (
1702 message = _(
1703 'When a patient is activated GNUmed checks the\n'
1704 'chart for encounters lacking any entries.\n'
1705 '\n'
1706 'Any such encounters older than what you set\n'
1707 'here will be removed from the medical record.\n'
1708 '\n'
1709 'To effectively disable removal of such encounters\n'
1710 'set this option to an improbable value.\n'
1711 ),
1712 option = 'encounter.ttl_if_empty',
1713 bias = 'user',
1714 default_value = '1 week',
1715 validator = is_valid
1716 )
1717
1722
1723 gmCfgWidgets.configure_string_option (
1724 message = _(
1725 'When a patient is activated GNUmed checks the\n'
1726 'age of the most recent encounter.\n'
1727 '\n'
1728 'If that encounter is younger than this age\n'
1729 'the existing encounter will be continued.\n'
1730 '\n'
1731 '(If it is really old a new encounter is\n'
1732 ' started, or else GNUmed will ask you.)\n'
1733 ),
1734 option = 'encounter.minimum_ttl',
1735 bias = 'user',
1736 default_value = '1 hour 30 minutes',
1737 validator = is_valid
1738 )
1739
1744
1745 gmCfgWidgets.configure_string_option (
1746 message = _(
1747 'When a patient is activated GNUmed checks the\n'
1748 'age of the most recent encounter.\n'
1749 '\n'
1750 'If that encounter is older than this age\n'
1751 'GNUmed will always start a new encounter.\n'
1752 '\n'
1753 '(If it is very recent the existing encounter\n'
1754 ' is continued, or else GNUmed will ask you.)\n'
1755 ),
1756 option = 'encounter.maximum_ttl',
1757 bias = 'user',
1758 default_value = '6 hours',
1759 validator = is_valid
1760 )
1761
1770
1771 gmCfgWidgets.configure_string_option (
1772 message = _(
1773 'At any time there can only be one open (ongoing)\n'
1774 'episode for each health issue.\n'
1775 '\n'
1776 'When you try to open (add data to) an episode on a health\n'
1777 'issue GNUmed will check for an existing open episode on\n'
1778 'that issue. If there is any it will check the age of that\n'
1779 'episode. The episode is closed if it has been dormant (no\n'
1780 'data added, that is) for the period of time (in days) you\n'
1781 'set here.\n'
1782 '\n'
1783 "If the existing episode hasn't been dormant long enough\n"
1784 'GNUmed will consult you what to do.\n'
1785 '\n'
1786 'Enter maximum episode dormancy in DAYS:'
1787 ),
1788 option = 'episode.ttl',
1789 bias = 'user',
1790 default_value = 60,
1791 validator = is_valid
1792 )
1793
1824
1839
1864
1876
1877 gmCfgWidgets.configure_string_option (
1878 message = _(
1879 'GNUmed can check for new releases being available. To do\n'
1880 'so it needs to load version information from an URL.\n'
1881 '\n'
1882 'The default URL is:\n'
1883 '\n'
1884 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1885 '\n'
1886 'but you can configure any other URL locally. Note\n'
1887 'that you must enter the location as a valid URL.\n'
1888 'Depending on the URL the client will need online\n'
1889 'access when checking for updates.'
1890 ),
1891 option = u'horstspace.update.url',
1892 bias = u'workplace',
1893 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1894 validator = is_valid
1895 )
1896
1914
1931
1948
1959
1960 gmCfgWidgets.configure_string_option (
1961 message = _(
1962 'GNUmed can show the document review dialog after\n'
1963 'calling the appropriate viewer for that document.\n'
1964 '\n'
1965 'Select the conditions under which you want\n'
1966 'GNUmed to do so:\n'
1967 '\n'
1968 ' 0: never display the review dialog\n'
1969 ' 1: always display the dialog\n'
1970 ' 2: only if there is no previous review by me\n'
1971 ' 3: only if there is no previous review at all\n'
1972 ' 4: only if there is no review by the responsible reviewer\n'
1973 '\n'
1974 'Note that if a viewer is configured to not block\n'
1975 'GNUmed during document display the review dialog\n'
1976 'will actually appear in parallel to the viewer.'
1977 ),
1978 option = u'horstspace.document_viewer.review_after_display',
1979 bias = u'user',
1980 default_value = 3,
1981 validator = is_valid
1982 )
1983
1985
1986
1987 master_data_lists = [
1988 'adr',
1989 'drugs',
1990 'codes',
1991 'communication_channel_types',
1992 'substances_in_brands',
1993 'substances',
1994 'labs',
1995 'form_templates',
1996 'doc_types',
1997 'enc_types',
1998 'text_expansions',
1999 'meta_test_types',
2000
2001 'patient_tags',
2002 'provinces',
2003 'db_translations',
2004 'test_types',
2005 'org_units',
2006 'vacc_indications',
2007 'vaccines',
2008 'workplaces'
2009 ]
2010
2011 master_data_list_names = {
2012 'adr': _('Addresses (likely slow)'),
2013 'drugs': _('Branded drugs (as marketed)'),
2014 'codes': _('Codes and their respective terms'),
2015 'communication_channel_types': _('Communication channel types'),
2016 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2017 'labs': _('Diagnostic organizations (path labs, ...)'),
2018 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2019 'doc_types': _('Document types'),
2020 'enc_types': _('Encounter types'),
2021 'text_expansions': _('Keyword based text expansion macros'),
2022 'meta_test_types': _('Meta test/measurement types'),
2023
2024 'patient_tags': _('Patient tags'),
2025 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2026 'db_translations': _('String translations in the database'),
2027 'test_types': _('Test/measurement types'),
2028 'org_units': _('Units of organizations (branches, sites, departments, parts, ...'),
2029 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2030 'vaccines': _('Vaccines'),
2031 'workplaces': _('Workplace profiles (which plugins to load)'),
2032 'substances': _('Consumable substances')
2033 }
2034
2035 map_list2handler = {
2036 'org_units': gmOrganizationWidgets.manage_org_units,
2037 'form_templates': gmFormWidgets.manage_form_templates,
2038 'doc_types': gmDocumentWidgets.manage_document_types,
2039 'text_expansions': gmProviderInboxWidgets.configure_keyword_text_expansion,
2040 'db_translations': gmI18nWidgets.manage_translations,
2041 'codes': gmCodingWidgets.browse_coded_terms,
2042 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2043 'provinces': gmPersonContactWidgets.manage_provinces,
2044 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins,
2045 'drugs': gmMedicationWidgets.manage_branded_drugs,
2046 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2047 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2048 'test_types': gmMeasurementWidgets.manage_measurement_types,
2049 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2050 'vaccines': gmVaccWidgets.manage_vaccines,
2051 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2052
2053 'adr': gmPersonContactWidgets.manage_addresses,
2054 'substances': gmMedicationWidgets.manage_consumable_substances,
2055 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2056 'communication_channel_types': gmPersonContactWidgets.manage_comm_channel_types
2057 }
2058
2059
2060 def edit(item):
2061 try: map_list2handler[item](parent = self)
2062 except KeyError: pass
2063 return False
2064
2065
2066 gmListWidgets.get_choices_from_list (
2067 parent = self,
2068 caption = _('Master data management'),
2069 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2070 data = master_data_lists,
2071 columns = [_('Select the list you want to manage:')],
2072 edit_callback = edit,
2073 single_selection = True,
2074 ignore_OK_button = True
2075 )
2076
2078
2079 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2080 if found:
2081 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2082 return
2083
2084 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2085 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking=False)
2086 return
2087
2088 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2089 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2090 if found:
2091 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2092 return
2093
2094 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2095
2097
2098 curr_pat = gmPerson.gmCurrentPatient()
2099
2100 arriba = gmArriba.cArriba()
2101 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2102 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2103 return
2104
2105
2106 if curr_pat is None:
2107 return
2108
2109 if arriba.pdf_result is None:
2110 return
2111
2112 doc = gmDocumentWidgets.save_file_as_new_document (
2113 parent = self,
2114 filename = arriba.pdf_result,
2115 document_type = _('risk assessment')
2116 )
2117
2118 try: os.remove(arriba.pdf_result)
2119 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2120
2121 if doc is None:
2122 return
2123
2124 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2125 doc.save()
2126
2127 try:
2128 open(arriba.xml_result).close()
2129 part = doc.add_part(file = arriba.xml_result)
2130 except StandardError:
2131 _log.exception('error accessing [%s]', arriba.xml_result)
2132 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2133
2134 if part is None:
2135 return
2136
2137 part['obj_comment'] = u'XML-Daten'
2138 part['filename'] = u'arriba-result.xml'
2139 part.save()
2140
2142
2143 dbcfg = gmCfg.cCfgSQL()
2144 cmd = dbcfg.get2 (
2145 option = u'external.tools.acs_risk_calculator_cmd',
2146 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2147 bias = 'user'
2148 )
2149
2150 if cmd is None:
2151 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2152 return
2153
2154 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
2155 try:
2156 subprocess.check_call (
2157 args = (cmd,),
2158 close_fds = True,
2159 cwd = cwd
2160 )
2161 except (OSError, ValueError, subprocess.CalledProcessError):
2162 _log.exception('there was a problem executing [%s]', cmd)
2163 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2164 return
2165
2166 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2167 for pdf in pdfs:
2168 try:
2169 open(pdf).close()
2170 except:
2171 _log.exception('error accessing [%s]', pdf)
2172 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2173 continue
2174
2175 doc = gmDocumentWidgets.save_file_as_new_document (
2176 parent = self,
2177 filename = pdf,
2178 document_type = u'risk assessment'
2179 )
2180
2181 try:
2182 os.remove(pdf)
2183 except StandardError:
2184 _log.exception('cannot remove [%s]', pdf)
2185
2186 if doc is None:
2187 continue
2188 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2189 doc.save()
2190
2191 return
2192
2194 dlg = gmSnellen.cSnellenCfgDlg()
2195 if dlg.ShowModal() != wx.ID_OK:
2196 return
2197
2198 frame = gmSnellen.cSnellenChart (
2199 width = dlg.vals[0],
2200 height = dlg.vals[1],
2201 alpha = dlg.vals[2],
2202 mirr = dlg.vals[3],
2203 parent = None
2204 )
2205 frame.CentreOnScreen(wx.BOTH)
2206
2207
2208 frame.Show(True)
2209
2210
2212 webbrowser.open (
2213 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
2214 new = False,
2215 autoraise = True
2216 )
2217
2220
2222 webbrowser.open (
2223 url = 'http://www.kompendium.ch',
2224 new = False,
2225 autoraise = True
2226 )
2227
2228
2229
2233
2234
2235
2237 wx.CallAfter(self.__save_screenshot)
2238 evt.Skip()
2239
2241
2242 time.sleep(0.5)
2243
2244 rect = self.GetRect()
2245
2246
2247 if sys.platform == 'linux2':
2248 client_x, client_y = self.ClientToScreen((0, 0))
2249 border_width = client_x - rect.x
2250 title_bar_height = client_y - rect.y
2251
2252 if self.GetMenuBar():
2253 title_bar_height /= 2
2254 rect.width += (border_width * 2)
2255 rect.height += title_bar_height + border_width
2256
2257 wdc = wx.ScreenDC()
2258 mdc = wx.MemoryDC()
2259 img = wx.EmptyBitmap(rect.width, rect.height)
2260 mdc.SelectObject(img)
2261 mdc.Blit (
2262 0, 0,
2263 rect.width, rect.height,
2264 wdc,
2265 rect.x, rect.y
2266 )
2267
2268
2269 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2270 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2271 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2272
2274
2275 raise ValueError('raised ValueError to test exception handling')
2276
2278 import wx.lib.inspection
2279 wx.lib.inspection.InspectionTool().Show()
2280
2282 webbrowser.open (
2283 url = 'https://bugs.launchpad.net/gnumed/',
2284 new = False,
2285 autoraise = True
2286 )
2287
2289 webbrowser.open (
2290 url = 'http://wiki.gnumed.de',
2291 new = False,
2292 autoraise = True
2293 )
2294
2296 webbrowser.open (
2297 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
2298 new = False,
2299 autoraise = True
2300 )
2301
2303 webbrowser.open (
2304 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
2305 new = False,
2306 autoraise = True
2307 )
2308
2315
2319
2322
2329
2334
2336 name = os.path.basename(gmLog2._logfile_name)
2337 name, ext = os.path.splitext(name)
2338 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2339 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2340
2341 dlg = wx.FileDialog (
2342 parent = self,
2343 message = _("Save current log as..."),
2344 defaultDir = new_path,
2345 defaultFile = new_name,
2346 wildcard = "%s (*.log)|*.log" % _("log files"),
2347 style = wx.SAVE
2348 )
2349 choice = dlg.ShowModal()
2350 new_name = dlg.GetPath()
2351 dlg.Destroy()
2352 if choice != wx.ID_OK:
2353 return True
2354
2355 _log.warning('syncing log file for backup to [%s]', new_name)
2356 gmLog2.flush()
2357 shutil.copy2(gmLog2._logfile_name, new_name)
2358 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2359
2362
2363
2364
2366 """This is the wx.EVT_CLOSE handler.
2367
2368 - framework still functional
2369 """
2370 _log.debug('gmTopLevelFrame.OnClose() start')
2371 self._clean_exit()
2372 self.Destroy()
2373 _log.debug('gmTopLevelFrame.OnClose() end')
2374 return True
2375
2381
2386
2394
2401
2408
2418
2426
2434
2442
2450
2459
2468
2476
2493
2496
2499
2501
2502 pat = gmPerson.gmCurrentPatient()
2503 if not pat.connected:
2504 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2505 return False
2506
2507 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2508
2509 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2510 gmTools.mkdir(aDefDir)
2511
2512 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2513 dlg = wx.FileDialog (
2514 parent = self,
2515 message = _("Save patient's EMR journal as..."),
2516 defaultDir = aDefDir,
2517 defaultFile = fname,
2518 wildcard = aWildcard,
2519 style = wx.SAVE
2520 )
2521 choice = dlg.ShowModal()
2522 fname = dlg.GetPath()
2523 dlg.Destroy()
2524 if choice != wx.ID_OK:
2525 return True
2526
2527 _log.debug('exporting EMR journal to [%s]' % fname)
2528
2529 exporter = gmPatientExporter.cEMRJournalExporter()
2530
2531 wx.BeginBusyCursor()
2532 try:
2533 fname = exporter.export_to_file(filename = fname)
2534 except:
2535 wx.EndBusyCursor()
2536 gmGuiHelpers.gm_show_error (
2537 _('Error exporting patient EMR as chronological journal.'),
2538 _('EMR journal export')
2539 )
2540 raise
2541 wx.EndBusyCursor()
2542
2543 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2544
2545 return True
2546
2553
2555 curr_pat = gmPerson.gmCurrentPatient()
2556 if not curr_pat.connected:
2557 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2558 return
2559
2560 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2561 if tag is None:
2562 return
2563
2564 tag = curr_pat.add_tag(tag['pk_tag_image'])
2565 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2566 comment = wx.GetTextFromUser (
2567 message = msg,
2568 caption = _('Editing tag comment'),
2569 default_value = gmTools.coalesce(tag['comment'], u''),
2570 parent = self
2571 )
2572
2573 if comment == u'':
2574 return
2575
2576 if comment.strip() == tag['comment']:
2577 return
2578
2579 if comment == u' ':
2580 tag['comment'] = None
2581 else:
2582 tag['comment'] = comment.strip()
2583
2584 tag.save()
2585
2595
2597 curr_pat = gmPerson.gmCurrentPatient()
2598 if not curr_pat.connected:
2599 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2600 return False
2601
2602 enc = 'cp850'
2603 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2604 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2605 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2606
2609
2617
2625
2628
2635
2639
2642
2645
2648
2651
2656
2658 """Cleanup helper.
2659
2660 - should ALWAYS be called when this program is
2661 to be terminated
2662 - ANY code that should be executed before a
2663 regular shutdown should go in here
2664 - framework still functional
2665 """
2666 _log.debug('gmTopLevelFrame._clean_exit() start')
2667
2668
2669 listener = gmBackendListener.gmBackendListener()
2670 try:
2671 listener.shutdown()
2672 except:
2673 _log.exception('cannot stop backend notifications listener thread')
2674
2675
2676 if _scripting_listener is not None:
2677 try:
2678 _scripting_listener.shutdown()
2679 except:
2680 _log.exception('cannot stop scripting listener thread')
2681
2682
2683 self.clock_update_timer.Stop()
2684 gmTimer.shutdown()
2685 gmPhraseWheel.shutdown()
2686
2687
2688 for call_back in self.__pre_exit_callbacks:
2689 try:
2690 call_back()
2691 except:
2692 print "*** pre-exit callback failed ***"
2693 print call_back
2694 _log.exception('callback [%s] failed', call_back)
2695
2696
2697 gmDispatcher.send(u'application_closing')
2698
2699
2700 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2701
2702
2703 curr_width, curr_height = self.GetClientSizeTuple()
2704 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2705 dbcfg = gmCfg.cCfgSQL()
2706 dbcfg.set (
2707 option = 'main.window.width',
2708 value = curr_width,
2709 workplace = gmSurgery.gmCurrentPractice().active_workplace
2710 )
2711 dbcfg.set (
2712 option = 'main.window.height',
2713 value = curr_height,
2714 workplace = gmSurgery.gmCurrentPractice().active_workplace
2715 )
2716
2717 if _cfg.get(option = 'debug'):
2718 print '---=== GNUmed shutdown ===---'
2719 try:
2720 print _('You have to manually close this window to finalize shutting down GNUmed.')
2721 print _('This is so that you can inspect the console output at your leisure.')
2722 except UnicodeEncodeError:
2723 print 'You have to manually close this window to finalize shutting down GNUmed.'
2724 print 'This is so that you can inspect the console output at your leisure.'
2725 print '---=== GNUmed shutdown ===---'
2726
2727
2728 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2729
2730
2731 import threading
2732 _log.debug("%s active threads", threading.activeCount())
2733 for t in threading.enumerate():
2734 _log.debug('thread %s', t)
2735
2736 _log.debug('gmTopLevelFrame._clean_exit() end')
2737
2738
2739
2741
2742 if _cfg.get(option = 'slave'):
2743 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2744 _cfg.get(option = 'slave personality'),
2745 _cfg.get(option = 'xml-rpc port')
2746 )
2747 else:
2748 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2749
2751 """Update title of main window based on template.
2752
2753 This gives nice tooltips on iconified GNUmed instances.
2754
2755 User research indicates that in the title bar people want
2756 the date of birth, not the age, so please stick to this
2757 convention.
2758 """
2759 args = {}
2760
2761 pat = gmPerson.gmCurrentPatient()
2762 if pat.connected:
2763 args['pat'] = u'%s %s %s (%s) #%d' % (
2764 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2765 pat['firstnames'],
2766 pat['lastnames'],
2767 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2768 pat['pk_identity']
2769 )
2770 else:
2771 args['pat'] = _('no patient')
2772
2773 args['prov'] = u'%s%s.%s' % (
2774 gmTools.coalesce(_provider['title'], u'', u'%s '),
2775 _provider['firstnames'][:1],
2776 _provider['lastnames']
2777 )
2778
2779 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2780
2781 self.SetTitle(self.__title_template % args)
2782
2783
2785 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2786 sb.SetStatusWidths([-1, 225])
2787
2788 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2789 self._cb_update_clock()
2790
2791 self.clock_update_timer.Start(milliseconds = 1000)
2792
2794 """Displays date and local time in the second slot of the status bar"""
2795 t = time.localtime(time.time())
2796 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2797 self.SetStatusText(st,1)
2798
2800 """Lock GNUmed client against unauthorized access"""
2801
2802
2803
2804 return
2805
2807 """Unlock the main notebook widgets
2808 As long as we are not logged into the database backend,
2809 all pages but the 'login' page of the main notebook widget
2810 are locked; i.e. not accessible by the user
2811 """
2812
2813
2814
2815
2816
2817 return
2818
2820 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2821
2823
2825
2826 self.__starting_up = True
2827
2828 gmExceptionHandlingWidgets.install_wx_exception_handler()
2829 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2830
2831
2832
2833
2834 self.SetAppName(u'gnumed')
2835 self.SetVendorName(u'The GNUmed Development Community.')
2836 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2837 paths.init_paths(wx = wx, app_name = u'gnumed')
2838
2839 if not self.__setup_prefs_file():
2840 return False
2841
2842 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2843
2844 self.__guibroker = gmGuiBroker.GuiBroker()
2845 self.__setup_platform()
2846
2847 if not self.__establish_backend_connection():
2848 return False
2849
2850 if not _cfg.get(option = 'skip-update-check'):
2851 self.__check_for_updates()
2852
2853 if _cfg.get(option = 'slave'):
2854 if not self.__setup_scripting_listener():
2855 return False
2856
2857
2858 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2859 frame.CentreOnScreen(wx.BOTH)
2860 self.SetTopWindow(frame)
2861 frame.Show(True)
2862
2863 if _cfg.get(option = 'debug'):
2864 self.RedirectStdio()
2865 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2866
2867
2868 print '---=== GNUmed startup ===---'
2869 print _('redirecting STDOUT/STDERR to this log window')
2870 print '---=== GNUmed startup ===---'
2871
2872 self.__setup_user_activity_timer()
2873 self.__register_events()
2874
2875 wx.CallAfter(self._do_after_init)
2876
2877 return True
2878
2880 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2881
2882 - after destroying all application windows and controls
2883 - before wx.Windows internal cleanup
2884 """
2885 _log.debug('gmApp.OnExit() start')
2886
2887 self.__shutdown_user_activity_timer()
2888
2889 if _cfg.get(option = 'debug'):
2890 self.RestoreStdio()
2891 sys.stdin = sys.__stdin__
2892 sys.stdout = sys.__stdout__
2893 sys.stderr = sys.__stderr__
2894
2895 _log.debug('gmApp.OnExit() end')
2896
2898 wx.Bell()
2899 wx.Bell()
2900 wx.Bell()
2901 _log.warning('unhandled event detected: QUERY_END_SESSION')
2902 _log.info('we should be saving ourselves from here')
2903 gmLog2.flush()
2904 print "unhandled event detected: QUERY_END_SESSION"
2905
2907 wx.Bell()
2908 wx.Bell()
2909 wx.Bell()
2910 _log.warning('unhandled event detected: END_SESSION')
2911 gmLog2.flush()
2912 print "unhandled event detected: END_SESSION"
2913
2924
2926 self.user_activity_detected = True
2927 evt.Skip()
2928
2930
2931 if self.user_activity_detected:
2932 self.elapsed_inactivity_slices = 0
2933 self.user_activity_detected = False
2934 self.elapsed_inactivity_slices += 1
2935 else:
2936 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2937
2938 pass
2939
2940 self.user_activity_timer.Start(oneShot = True)
2941
2942
2943
2945 try:
2946 kwargs['originated_in_database']
2947 print '==> got notification from database "%s":' % kwargs['signal']
2948 except KeyError:
2949 print '==> received signal from client: "%s"' % kwargs['signal']
2950
2951 del kwargs['signal']
2952 for key in kwargs.keys():
2953 print ' [%s]: %s' % (key, kwargs[key])
2954
2956 print "wx.lib.pubsub message:"
2957 print msg.topic
2958 print msg.data
2959
2965
2967 self.user_activity_detected = True
2968 self.elapsed_inactivity_slices = 0
2969
2970 self.max_user_inactivity_slices = 15
2971 self.user_activity_timer = gmTimer.cTimer (
2972 callback = self._on_user_activity_timer_expired,
2973 delay = 2000
2974 )
2975 self.user_activity_timer.Start(oneShot=True)
2976
2978 try:
2979 self.user_activity_timer.Stop()
2980 del self.user_activity_timer
2981 except:
2982 pass
2983
2985 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2986 wx.EVT_END_SESSION(self, self._on_end_session)
2987
2988
2989
2990
2991
2992 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2993
2994 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2995 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2996
2997 if _cfg.get(option = 'debug'):
2998 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
2999 _log.debug('connected old signal monitor')
3000 wx.lib.pubsub.Publisher().subscribe(listener = self._signal_debugging_monitor_pubsub)
3001 _log.debug('connected wx.lib.pubsub based signal monitor for all topics')
3002
3003
3004
3005
3006
3007
3023
3025 """Handle all the database related tasks necessary for startup."""
3026
3027
3028 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
3029
3030 from Gnumed.wxpython import gmAuthWidgets
3031 connected = gmAuthWidgets.connect_to_database (
3032 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
3033 require_version = not override
3034 )
3035 if not connected:
3036 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
3037 return False
3038
3039
3040 try:
3041 global _provider
3042 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
3043 except ValueError:
3044 account = gmPG2.get_current_user()
3045 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
3046 msg = _(
3047 'The database account [%s] cannot be used as a\n'
3048 'staff member login for GNUmed. There was an\n'
3049 'error retrieving staff details for it.\n\n'
3050 'Please ask your administrator for help.\n'
3051 ) % account
3052 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
3053 return False
3054
3055
3056 tmp = '%s%s %s (%s = %s)' % (
3057 gmTools.coalesce(_provider['title'], ''),
3058 _provider['firstnames'],
3059 _provider['lastnames'],
3060 _provider['short_alias'],
3061 _provider['db_user']
3062 )
3063 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
3064
3065
3066 surgery = gmSurgery.gmCurrentPractice()
3067 msg = surgery.db_logon_banner
3068 if msg.strip() != u'':
3069
3070 login = gmPG2.get_default_login()
3071 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
3072 login.database,
3073 gmTools.coalesce(login.host, u'localhost')
3074 ))
3075 msg = auth + msg + u'\n\n'
3076
3077 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3078 None,
3079
3080 -1,
3081 caption = _('Verifying database'),
3082 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3083 button_defs = [
3084 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3085 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3086 ]
3087 )
3088 go_on = dlg.ShowModal()
3089 dlg.Destroy()
3090 if go_on != wx.ID_YES:
3091 _log.info('user decided to not connect to this database')
3092 return False
3093
3094
3095 self.__check_db_lang()
3096
3097 return True
3098
3100 """Setup access to a config file for storing preferences."""
3101
3102 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3103
3104 candidates = []
3105 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3106 if explicit_file is not None:
3107 candidates.append(explicit_file)
3108
3109 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3110 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3111 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3112
3113 prefs_file = None
3114 for candidate in candidates:
3115 try:
3116 open(candidate, 'a+').close()
3117 prefs_file = candidate
3118 break
3119 except IOError:
3120 continue
3121
3122 if prefs_file is None:
3123 msg = _(
3124 'Cannot find configuration file in any of:\n'
3125 '\n'
3126 ' %s\n'
3127 'You may need to use the comand line option\n'
3128 '\n'
3129 ' --conf-file=<FILE>'
3130 ) % '\n '.join(candidates)
3131 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3132 return False
3133
3134 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3135 _log.info('user preferences file: %s', prefs_file)
3136
3137 return True
3138
3140
3141 from socket import error as SocketError
3142 from Gnumed.pycommon import gmScriptingListener
3143 from Gnumed.wxpython import gmMacro
3144
3145 slave_personality = gmTools.coalesce (
3146 _cfg.get (
3147 group = u'workplace',
3148 option = u'slave personality',
3149 source_order = [
3150 ('explicit', 'return'),
3151 ('workbase', 'return'),
3152 ('user', 'return'),
3153 ('system', 'return')
3154 ]
3155 ),
3156 u'gnumed-client'
3157 )
3158 _cfg.set_option(option = 'slave personality', value = slave_personality)
3159
3160
3161 port = int (
3162 gmTools.coalesce (
3163 _cfg.get (
3164 group = u'workplace',
3165 option = u'xml-rpc port',
3166 source_order = [
3167 ('explicit', 'return'),
3168 ('workbase', 'return'),
3169 ('user', 'return'),
3170 ('system', 'return')
3171 ]
3172 ),
3173 9999
3174 )
3175 )
3176 _cfg.set_option(option = 'xml-rpc port', value = port)
3177
3178 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3179 global _scripting_listener
3180 try:
3181 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3182 except SocketError, e:
3183 _log.exception('cannot start GNUmed XML-RPC server')
3184 gmGuiHelpers.gm_show_error (
3185 aMessage = (
3186 'Cannot start the GNUmed server:\n'
3187 '\n'
3188 ' [%s]'
3189 ) % e,
3190 aTitle = _('GNUmed startup')
3191 )
3192 return False
3193
3194 return True
3195
3216
3218 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3219 _log.warning("system locale is undefined (probably meaning 'C')")
3220 return True
3221
3222
3223 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3224 db_lang = rows[0]['lang']
3225
3226 if db_lang is None:
3227 _log.debug("database locale currently not set")
3228 msg = _(
3229 "There is no language selected in the database for user [%s].\n"
3230 "Your system language is currently set to [%s].\n\n"
3231 "Do you want to set the database language to '%s' ?\n\n"
3232 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3233 checkbox_msg = _('Remember to ignore missing language')
3234 else:
3235 _log.debug("current database locale: [%s]" % db_lang)
3236 msg = _(
3237 "The currently selected database language ('%s') does\n"
3238 "not match the current system language ('%s').\n"
3239 "\n"
3240 "Do you want to set the database language to '%s' ?\n"
3241 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3242 checkbox_msg = _('Remember to ignore language mismatch')
3243
3244
3245 if db_lang == gmI18N.system_locale_level['full']:
3246 _log.debug('Database locale (%s) up to date.' % db_lang)
3247 return True
3248 if db_lang == gmI18N.system_locale_level['country']:
3249 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3250 return True
3251 if db_lang == gmI18N.system_locale_level['language']:
3252 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3253 return True
3254
3255 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3256
3257
3258 ignored_sys_lang = _cfg.get (
3259 group = u'backend',
3260 option = u'ignored mismatching system locale',
3261 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3262 )
3263
3264
3265 if gmI18N.system_locale == ignored_sys_lang:
3266 _log.info('configured to ignore system-to-database locale mismatch')
3267 return True
3268
3269
3270 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3271 None,
3272 -1,
3273 caption = _('Checking database language settings'),
3274 question = msg,
3275 button_defs = [
3276 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3277 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3278 ],
3279 show_checkbox = True,
3280 checkbox_msg = checkbox_msg,
3281 checkbox_tooltip = _(
3282 'Checking this will make GNUmed remember your decision\n'
3283 'until the system language is changed.\n'
3284 '\n'
3285 'You can also reactivate this inquiry by removing the\n'
3286 'corresponding "ignore" option from the configuration file\n'
3287 '\n'
3288 ' [%s]'
3289 ) % _cfg.get(option = 'user_preferences_file')
3290 )
3291 decision = dlg.ShowModal()
3292 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3293 dlg.Destroy()
3294
3295 if decision == wx.ID_NO:
3296 if not remember_ignoring_problem:
3297 return True
3298 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3299 gmCfg2.set_option_in_INI_file (
3300 filename = _cfg.get(option = 'user_preferences_file'),
3301 group = 'backend',
3302 option = 'ignored mismatching system locale',
3303 value = gmI18N.system_locale
3304 )
3305 return True
3306
3307
3308 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3309 if len(lang) > 0:
3310
3311
3312 rows, idx = gmPG2.run_rw_queries (
3313 link_obj = None,
3314 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3315 return_data = True
3316 )
3317 if rows[0][0]:
3318 _log.debug("Successfully set database language to [%s]." % lang)
3319 else:
3320 _log.error('Cannot set database language to [%s].' % lang)
3321 continue
3322 return True
3323
3324
3325 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3326 gmPG2.run_rw_queries(queries = [{
3327 'cmd': u'select i18n.force_curr_lang(%s)',
3328 'args': [gmI18N.system_locale_level['country']]
3329 }])
3330
3331 return True
3332
3334 try:
3335 kwargs['originated_in_database']
3336 print '==> got notification from database "%s":' % kwargs['signal']
3337 except KeyError:
3338 print '==> received signal from client: "%s"' % kwargs['signal']
3339
3340 del kwargs['signal']
3341 for key in kwargs.keys():
3342
3343 try: print ' [%s]: %s' % (key, kwargs[key])
3344 except: print 'cannot print signal information'
3345
3347
3348 try:
3349 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3350 print ' data: %s' % msg.data
3351 print msg
3352 except: print 'problem printing pubsub message information'
3353
3355
3356 if _cfg.get(option = 'debug'):
3357 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3358 _log.debug('gmDispatcher signal monitor activated')
3359 wx.lib.pubsub.Publisher().subscribe (
3360 listener = _signal_debugging_monitor_pubsub
3361
3362 )
3363 _log.debug('wx.lib.pubsub signal monitor activated')
3364
3365 wx.InitAllImageHandlers()
3366
3367
3368
3369 app = gmApp(redirect = False, clearSigInt = False)
3370 app.MainLoop()
3371
3372
3373
3374 if __name__ == '__main__':
3375
3376 from GNUmed.pycommon import gmI18N
3377 gmI18N.activate_locale()
3378 gmI18N.install_domain()
3379
3380 _log.info('Starting up as main module.')
3381 main()
3382
3383
3384