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 from Gnumed.wxpython import gmContactWidgets
75 from Gnumed.wxpython import gmAddressWidgets
76
77
78 try:
79 _('dummy-no-need-to-translate-but-make-epydoc-happy')
80 except NameError:
81 _ = lambda x:x
82
83 _cfg = gmCfg2.gmCfgData()
84 _provider = None
85 _scripting_listener = None
86
87 _log = logging.getLogger('gm.main')
88 _log.info(__version__)
89 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
90
91
93 """GNUmed client's main windows frame.
94
95 This is where it all happens. Avoid popping up any other windows.
96 Most user interaction should happen to and from widgets within this frame
97 """
98
99 - def __init__(self, parent, id, title, size=wx.DefaultSize):
100 """You'll have to browse the source to understand what the constructor does
101 """
102 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
103
104 self.__setup_font()
105
106 self.__gb = gmGuiBroker.GuiBroker()
107 self.__pre_exit_callbacks = []
108 self.bar_width = -1
109 self.menu_id2plugin = {}
110
111 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
112
113 self.__setup_main_menu()
114 self.setup_statusbar()
115 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
116 gmTools.coalesce(_provider['title'], ''),
117 _provider['firstnames'][:1],
118 _provider['lastnames'],
119 _provider['short_alias'],
120 _provider['db_user']
121 ))
122
123 self.__set_window_title_template()
124 self.__update_window_title()
125
126
127
128
129
130 self.SetIcon(gmTools.get_icon(wx = wx))
131
132 self.__register_events()
133
134 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
135 self.vbox = wx.BoxSizer(wx.VERTICAL)
136 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
137
138 self.SetAutoLayout(True)
139 self.SetSizerAndFit(self.vbox)
140
141
142
143
144
145 self.__set_GUI_size()
146
147
149
150 font = self.GetFont()
151 _log.debug('system default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
152
153 desired_font_face = _cfg.get (
154 group = u'workplace',
155 option = u'client font',
156 source_order = [
157 ('explicit', 'return'),
158 ('workbase', 'return'),
159 ('local', 'return'),
160 ('user', 'return'),
161 ('system', 'return')
162 ]
163 )
164
165 fonts2try = []
166 if desired_font_face is not None:
167 _log.info('client is configured to use font [%s]', desired_font_face)
168 fonts2try.append(desired_font_face)
169
170 if wx.Platform == '__WXMSW__':
171 sane_font_face = u'DejaVu Sans'
172 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face)
173 fonts2try.append(sane_font_face)
174
175 if len(fonts2try) == 0:
176 return
177
178 for font_face in fonts2try:
179 success = font.SetFaceName(font_face)
180 if success:
181 self.SetFont(font)
182 _log.debug('switched font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
183 return
184 font = self.GetFont()
185 _log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face)
186
187 return
188
190 """Try to get previous window size from backend."""
191
192 cfg = gmCfg.cCfgSQL()
193
194
195 width = int(cfg.get2 (
196 option = 'main.window.width',
197 workplace = gmSurgery.gmCurrentPractice().active_workplace,
198 bias = 'workplace',
199 default = 800
200 ))
201
202
203 height = int(cfg.get2 (
204 option = 'main.window.height',
205 workplace = gmSurgery.gmCurrentPractice().active_workplace,
206 bias = 'workplace',
207 default = 600
208 ))
209
210 dw = wx.DisplaySize()[0]
211 dh = wx.DisplaySize()[1]
212
213 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
214 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
215 _log.debug('previous GUI size [%s:%s]', width, height)
216
217
218 if width > dw:
219 _log.debug('adjusting GUI width from %s to %s', width, dw)
220 width = dw
221
222 if height > dh:
223 _log.debug('adjusting GUI height from %s to %s', height, dh)
224 height = dh
225
226
227 if width < 100:
228 _log.debug('adjusting GUI width to minimum of 100 pixel')
229 width = 100
230 if height < 100:
231 _log.debug('adjusting GUI height to minimum of 100 pixel')
232 height = 100
233
234 _log.info('setting GUI to size [%s:%s]', width, height)
235
236 self.SetClientSize(wx.Size(width, height))
237
239 """Create the main menu entries.
240
241 Individual entries are farmed out to the modules.
242
243 menu item template:
244
245 item = menu_emr_edit.Append(-1, _(''), _(''))
246 self.Bind(wx.EVT_MENU, self__on_, item)
247 """
248 global wx
249 self.mainmenu = wx.MenuBar()
250 self.__gb['main.mainmenu'] = self.mainmenu
251
252
253 menu_gnumed = wx.Menu()
254
255 self.menu_plugins = wx.Menu()
256 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
257
258 ID = wx.NewId()
259 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
260 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
261
262 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
263 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
264
265
266 menu_gnumed.AppendSeparator()
267
268
269 menu_config = wx.Menu()
270
271 item = menu_config.Append(-1, _('List configuration'), _('List all configuration items stored in the database.'))
272 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
273
274
275 menu_cfg_db = wx.Menu()
276
277 ID = wx.NewId()
278 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
279 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
280
281 ID = wx.NewId()
282 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
283 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
284
285 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
286
287
288 menu_cfg_client = wx.Menu()
289
290 ID = wx.NewId()
291 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
292 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
293
294 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
295 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
296
297 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
298
299
300 menu_cfg_ui = wx.Menu()
301
302
303 menu_cfg_doc = wx.Menu()
304
305 ID = wx.NewId()
306 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
307 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
308
309 ID = wx.NewId()
310 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
311 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
312
313 ID = wx.NewId()
314 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
315 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
316
317 item = menu_cfg_doc.Append(-1, _('Generate UUID'), _('Whether to generate UUIDs for new documents.'))
318 self.Bind(wx.EVT_MENU, self.__on_configure_generate_doc_uuid, item)
319
320 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
321
322
323 menu_cfg_update = wx.Menu()
324
325 ID = wx.NewId()
326 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
327 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
328
329 ID = wx.NewId()
330 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
331 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
332
333 ID = wx.NewId()
334 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
335 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
336
337 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
338
339
340 menu_cfg_pat_search = wx.Menu()
341
342 ID = wx.NewId()
343 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
344 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
345
346 ID = wx.NewId()
347 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
348 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
349
350 ID = wx.NewId()
351 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
352 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
353
354 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
355 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
356
357 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
358 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
359
360 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
361
362
363 menu_cfg_soap_editing = wx.Menu()
364
365 ID = wx.NewId()
366 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
367 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
368
369 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.'))
370 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item)
371
372 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
373
374 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
375
376
377 menu_cfg_ext_tools = wx.Menu()
378
379
380
381
382
383 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
384 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
385
386 ID = wx.NewId()
387 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
388 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
389
390 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
391 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
392
393 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
394 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
395
396 item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.'))
397 self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item)
398
399 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
400 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
401
402 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
403 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
404
405 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
406 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
407
408 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
409 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
410
411 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
412
413
414 menu_cfg_emr = wx.Menu()
415
416 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
417 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
418
419 item = menu_cfg_emr.Append(-1, _('Primary doctor'), _('Select the primary doctor to fall back to for patients without one.'))
420 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
421
422
423 menu_cfg_encounter = wx.Menu()
424
425 ID = wx.NewId()
426 menu_cfg_encounter.Append(ID, _('Edit before patient change'), _('Edit encounter details before change of patient.'))
427 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
428
429 ID = wx.NewId()
430 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
431 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
432
433 ID = wx.NewId()
434 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
435 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
436
437 ID = wx.NewId()
438 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
439 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
440
441 ID = wx.NewId()
442 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
443 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
444
445 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
446
447
448 menu_cfg_episode = wx.Menu()
449
450 ID = wx.NewId()
451 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
452 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
453
454 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
455 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
456 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
457
458
459 menu_master_data = wx.Menu()
460
461 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
462 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
463
464 item = menu_master_data.Append(-1, _('Install data packs'), _('Install reference data from data packs.'))
465 self.Bind(wx.EVT_MENU, self.__on_install_data_packs, item)
466
467 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
468 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
469
470
471
472
473 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
474 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
475
476 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
477
478
479 menu_users = wx.Menu()
480
481 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
482 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
483
484 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
485 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
486
487 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
488 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
489
490 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
491
492
493 menu_gnumed.AppendSeparator()
494
495 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
496 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
497
498 self.mainmenu.Append(menu_gnumed, '&GNUmed')
499
500
501 menu_person = wx.Menu()
502
503 ID_CREATE_PATIENT = wx.NewId()
504 menu_person.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
505 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
506
507 ID_LOAD_EXT_PAT = wx.NewId()
508 menu_person.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
509 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
510
511 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
512 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
513
514 ID_DEL_PAT = wx.NewId()
515 menu_person.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
516 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
517
518 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
519 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
520
521 menu_person.AppendSeparator()
522
523 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
524 menu_person.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
525 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
526
527
528 ID = wx.NewId()
529 menu_person.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
530 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
531
532 menu_person.AppendSeparator()
533
534 self.mainmenu.Append(menu_person, '&Person')
535 self.__gb['main.patientmenu'] = menu_person
536
537
538 menu_emr = wx.Menu()
539
540
541 menu_emr_edit = wx.Menu()
542
543 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'))
544 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
545
546 item = menu_emr_edit.Append(-1, _('&Episode'), _('Add an episode of illness to the EMR of the active patient'))
547 self.Bind(wx.EVT_MENU, self.__on_add_episode, item)
548
549 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
550 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
551
552 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
553 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
554
555 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
556 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
557
558 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
559 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
560
561 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
562 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
563
564 item = menu_emr_edit.Append(-1, _('&Measurements'), _('Add (a) measurement result(s) for the current patient.'))
565 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
566
567 item = menu_emr_edit.Append(-1, _('&Vaccinations'), _('Add (a) vaccination(s) for the current patient.'))
568 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
569
570 item = menu_emr_edit.Append(-1, _('&Family history (FHx)'), _('Manage family history.'))
571 self.Bind(wx.EVT_MENU, self.__on_manage_fhx, item)
572
573 item = menu_emr_edit.Append(-1, _('&Encounters'), _('List all encounters including empty ones.'))
574 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
575
576 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
577
578
579 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
580 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
581
582 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
583 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
584
585 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
586 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
587
588
589
590
591
592 item = menu_emr.Append(-1, _('Statistics'), _('Show a high-level statistic summary of the EMR.'))
593 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
594
595
596
597
598 menu_emr.AppendSeparator()
599
600
601 menu_emr_export = wx.Menu()
602
603 ID_EXPORT_EMR_ASCII = wx.NewId()
604 menu_emr_export.Append (
605 ID_EXPORT_EMR_ASCII,
606 _('Text document'),
607 _("Export the EMR of the active patient into a text file")
608 )
609 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
610
611 ID_EXPORT_EMR_JOURNAL = wx.NewId()
612 menu_emr_export.Append (
613 ID_EXPORT_EMR_JOURNAL,
614 _('Journal'),
615 _("Export the EMR of the active patient as a chronological journal into a text file")
616 )
617 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
618
619 ID_EXPORT_MEDISTAR = wx.NewId()
620 menu_emr_export.Append (
621 ID_EXPORT_MEDISTAR,
622 _('MEDISTAR import format'),
623 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
624 )
625 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
626
627 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
628
629 menu_emr.AppendSeparator()
630
631 self.mainmenu.Append(menu_emr, _("&EMR"))
632 self.__gb['main.emrmenu'] = menu_emr
633
634
635 menu_paperwork = wx.Menu()
636
637 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
638 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
639
640 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
641
642
643 self.menu_tools = wx.Menu()
644
645 ID_DICOM_VIEWER = wx.NewId()
646 viewer = _('no viewer installed')
647 if gmShellAPI.detect_external_binary(binary = 'ginkgocadx')[0]:
648 viewer = u'Ginkgo CADx'
649 elif os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
650 viewer = u'OsiriX'
651 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
652 viewer = u'Aeskulap'
653 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
654 viewer = u'AMIDE'
655 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
656 viewer = u'DicomScope'
657 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
658 viewer = u'(x)medcon'
659 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)
660 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
661 if viewer == _('no viewer installed'):
662 _log.info('neither of Ginkgo CADx / OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
663 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
664
665
666
667
668
669 ID = wx.NewId()
670 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
671 wx.EVT_MENU(self, ID, self.__on_snellen)
672
673 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
674 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
675
676 item = self.menu_tools.Append(-1, _('arriba'), _('arriba: cardiovascular risk assessment (%s).') % u'www.arriba-hausarzt.de')
677 self.Bind(wx.EVT_MENU, self.__on_arriba, item)
678
679 self.menu_tools.AppendSeparator()
680
681 self.mainmenu.Append(self.menu_tools, _("&Tools"))
682 self.__gb['main.toolsmenu'] = self.menu_tools
683
684
685 menu_knowledge = wx.Menu()
686
687
688 menu_drug_dbs = wx.Menu()
689
690 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
691 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
692
693
694
695
696
697
698 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
699
700 menu_id = wx.NewId()
701 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
702 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
703
704
705
706
707 ID_MEDICAL_LINKS = wx.NewId()
708 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
709 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
710
711 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
712 self.__gb['main.knowledgemenu'] = menu_knowledge
713
714
715 self.menu_office = wx.Menu()
716
717 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
718 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
719
720 self.menu_office.AppendSeparator()
721
722 self.mainmenu.Append(self.menu_office, _('&Office'))
723 self.__gb['main.officemenu'] = self.menu_office
724
725
726 help_menu = wx.Menu()
727
728 ID = wx.NewId()
729 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
730 wx.EVT_MENU(self, ID, self.__on_display_wiki)
731
732 ID = wx.NewId()
733 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
734 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
735
736 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
737 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
738
739 menu_debugging = wx.Menu()
740
741 ID_SCREENSHOT = wx.NewId()
742 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
743 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
744
745 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
746 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
747
748 ID = wx.NewId()
749 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
750 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
751
752 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
753 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
754
755 ID = wx.NewId()
756 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
757 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
758
759 ID_UNBLOCK = wx.NewId()
760 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
761 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
762
763 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
764 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
765
766
767
768
769 if _cfg.get(option = 'debug'):
770 ID_TOGGLE_PAT_LOCK = wx.NewId()
771 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
772 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
773
774 ID_TEST_EXCEPTION = wx.NewId()
775 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
776 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
777
778 ID = wx.NewId()
779 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
780 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
781 try:
782 import wx.lib.inspection
783 except ImportError:
784 menu_debugging.Enable(id = ID, enable = False)
785
786 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
787
788 help_menu.AppendSeparator()
789
790 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
791 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
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 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors'))
797 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item)
798
799 help_menu.AppendSeparator()
800
801 self.mainmenu.Append(help_menu, _("&Help"))
802
803 self.__gb['main.helpmenu'] = help_menu
804
805
806 self.SetMenuBar(self.mainmenu)
807
810
811
812
814 """register events we want to react to"""
815
816 wx.EVT_CLOSE(self, self.OnClose)
817 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
818 wx.EVT_END_SESSION(self, self._on_end_session)
819
820 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
821 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
822 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
823 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
824 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
825 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
826 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
827 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
828
829 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
830
831 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
832
833 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
834
835 _log.debug('registering plugin with menu system')
836 _log.debug(' generic name: %s', plugin_name)
837 _log.debug(' class name: %s', class_name)
838 _log.debug(' specific menu: %s', menu_name)
839 _log.debug(' menu item: %s', menu_item_name)
840
841
842 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
843 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
844 self.menu_id2plugin[item.Id] = class_name
845
846
847 if menu_name is not None:
848 menu = self.__gb['main.%smenu' % menu_name]
849 item = menu.Append(-1, menu_item_name, menu_help_string)
850 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
851 self.menu_id2plugin[item.Id] = class_name
852
853 return True
854
856 gmDispatcher.send (
857 signal = u'display_widget',
858 name = self.menu_id2plugin[evt.Id]
859 )
860
862 wx.Bell()
863 wx.Bell()
864 wx.Bell()
865 _log.warning('unhandled event detected: QUERY_END_SESSION')
866 _log.info('we should be saving ourselves from here')
867 gmLog2.flush()
868 print "unhandled event detected: QUERY_END_SESSION"
869
871 wx.Bell()
872 wx.Bell()
873 wx.Bell()
874 _log.warning('unhandled event detected: END_SESSION')
875 gmLog2.flush()
876 print "unhandled event detected: END_SESSION"
877
879 if not callable(callback):
880 raise TypeError(u'callback [%s] not callable' % callback)
881
882 self.__pre_exit_callbacks.append(callback)
883
884 - def _on_set_statustext_pubsub(self, context=None):
885 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
886 wx.CallAfter(self.SetStatusText, msg)
887
888 try:
889 if context.data['beep']:
890 wx.Bell()
891 except KeyError:
892 pass
893
894 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
895
896 if msg is None:
897 msg = _('programmer forgot to specify status message')
898
899 if loglevel is not None:
900 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
901
902 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
903 wx.CallAfter(self.SetStatusText, msg)
904
905 if beep:
906 wx.Bell()
907
909 wx.CallAfter(self.__on_db_maintenance_warning)
910
912
913 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
914 wx.Bell()
915 if not wx.GetApp().IsActive():
916 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
917
918 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
919
920 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
921 None,
922 -1,
923 caption = _('Database shutdown warning'),
924 question = _(
925 'The database will be shut down for maintenance\n'
926 'in a few minutes.\n'
927 '\n'
928 'In order to not suffer any loss of data you\n'
929 'will need to save your current work and log\n'
930 'out of this GNUmed client.\n'
931 ),
932 button_defs = [
933 {
934 u'label': _('Close now'),
935 u'tooltip': _('Close this GNUmed client immediately.'),
936 u'default': False
937 },
938 {
939 u'label': _('Finish work'),
940 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
941 u'default': True
942 }
943 ]
944 )
945 decision = dlg.ShowModal()
946 if decision == wx.ID_YES:
947 top_win = wx.GetApp().GetTopWindow()
948 wx.CallAfter(top_win.Close)
949
951 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
952
954
955 if not wx.GetApp().IsActive():
956 if urgent:
957 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
958 else:
959 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
960
961 if msg is not None:
962 self.SetStatusText(msg)
963
964 if urgent:
965 wx.Bell()
966
967 gmHooks.run_hook_script(hook = u'request_user_attention')
968
970 wx.CallAfter(self.__on_pat_name_changed)
971
973 self.__update_window_title()
974
976 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
977
979 self.__update_window_title()
980 try:
981 gmHooks.run_hook_script(hook = u'post_patient_activation')
982 except:
983 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
984 raise
985
987 return self.__sanity_check_encounter()
988
1050
1051
1052
1055
1063
1064
1065
1080
1103
1105 from Gnumed.wxpython import gmAbout
1106 contribs = gmAbout.cContributorsDlg (
1107 parent = self,
1108 id = -1,
1109 title = _('GNUmed contributors'),
1110 size = wx.Size(400,600),
1111 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1112 )
1113 contribs.ShowModal()
1114 del contribs
1115 del gmAbout
1116
1117
1118
1120 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1121 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1122 self.Close(True)
1123 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1124
1127
1129 send = gmGuiHelpers.gm_show_question (
1130 _('This will send a notification about database downtime\n'
1131 'to all GNUmed clients connected to your database.\n'
1132 '\n'
1133 'Do you want to send the notification ?\n'
1134 ),
1135 _('Announcing database maintenance downtime')
1136 )
1137 if not send:
1138 return
1139 gmPG2.send_maintenance_notification()
1140
1141
1144
1145
1146
1159
1160 gmCfgWidgets.configure_string_option (
1161 message = _(
1162 'Some network installations cannot cope with loading\n'
1163 'documents of arbitrary size in one piece from the\n'
1164 'database (mainly observed on older Windows versions)\n.'
1165 '\n'
1166 'Under such circumstances documents need to be retrieved\n'
1167 'in chunks and reassembled on the client.\n'
1168 '\n'
1169 'Here you can set the size (in Bytes) above which\n'
1170 'GNUmed will retrieve documents in chunks. Setting this\n'
1171 'value to 0 will disable the chunking protocol.'
1172 ),
1173 option = 'horstspace.blob_export_chunk_size',
1174 bias = 'workplace',
1175 default_value = 1024 * 1024,
1176 validator = is_valid
1177 )
1178
1179
1180
1248
1252
1253
1254
1263
1264 gmCfgWidgets.configure_string_option (
1265 message = _(
1266 'When GNUmed cannot find an OpenOffice server it\n'
1267 'will try to start one. OpenOffice, however, needs\n'
1268 'some time to fully start up.\n'
1269 '\n'
1270 'Here you can set the time for GNUmed to wait for OOo.\n'
1271 ),
1272 option = 'external.ooo.startup_settle_time',
1273 bias = 'workplace',
1274 default_value = 2.0,
1275 validator = is_valid
1276 )
1277
1280
1295
1296 gmCfgWidgets.configure_string_option (
1297 message = _(
1298 'GNUmed will use this URL to access a website which lets\n'
1299 'you report an adverse drug reaction (ADR).\n'
1300 '\n'
1301 'If you leave this empty it will fall back\n'
1302 'to an URL for reporting ADRs in Germany.'
1303 ),
1304 option = 'external.urls.report_ADR',
1305 bias = 'user',
1306 default_value = german_default,
1307 validator = is_valid
1308 )
1309
1323
1324 gmCfgWidgets.configure_string_option (
1325 message = _(
1326 'GNUmed will use this URL to access a website which lets\n'
1327 'you report an adverse vaccination reaction (vADR).\n'
1328 '\n'
1329 'If you set it to a specific address that URL must be\n'
1330 'accessible now. If you leave it empty it will fall back\n'
1331 'to the URL for reporting other adverse drug reactions.'
1332 ),
1333 option = 'external.urls.report_vaccine_ADR',
1334 bias = 'user',
1335 default_value = german_default,
1336 validator = is_valid
1337 )
1338
1352
1353 gmCfgWidgets.configure_string_option (
1354 message = _(
1355 'GNUmed will use this URL to access an encyclopedia of\n'
1356 'measurement/lab methods from within the measurments grid.\n'
1357 '\n'
1358 'You can leave this empty but to set it to a specific\n'
1359 'address the URL must be accessible now.'
1360 ),
1361 option = 'external.urls.measurements_encyclopedia',
1362 bias = 'user',
1363 default_value = german_default,
1364 validator = is_valid
1365 )
1366
1380
1381 gmCfgWidgets.configure_string_option (
1382 message = _(
1383 'GNUmed will use this URL to access a page showing\n'
1384 'vaccination schedules.\n'
1385 '\n'
1386 'You can leave this empty but to set it to a specific\n'
1387 'address the URL must be accessible now.'
1388 ),
1389 option = 'external.urls.vaccination_plans',
1390 bias = 'user',
1391 default_value = german_default,
1392 validator = is_valid
1393 )
1394
1407
1408 gmCfgWidgets.configure_string_option (
1409 message = _(
1410 'Enter the shell command with which to start the\n'
1411 'the ACS risk assessment calculator.\n'
1412 '\n'
1413 'GNUmed will try to verify the path which may,\n'
1414 'however, fail if you are using an emulator such\n'
1415 'as Wine. Nevertheless, starting the calculator\n'
1416 'will work as long as the shell command is correct\n'
1417 'despite the failing test.'
1418 ),
1419 option = 'external.tools.acs_risk_calculator_cmd',
1420 bias = 'user',
1421 validator = is_valid
1422 )
1423
1426
1439
1440 gmCfgWidgets.configure_string_option (
1441 message = _(
1442 'Enter the shell command with which to start\n'
1443 'the FreeDiams drug database frontend.\n'
1444 '\n'
1445 'GNUmed will try to verify that path.'
1446 ),
1447 option = 'external.tools.freediams_cmd',
1448 bias = 'workplace',
1449 default_value = None,
1450 validator = is_valid
1451 )
1452
1465
1466 gmCfgWidgets.configure_string_option (
1467 message = _(
1468 'Enter the shell command with which to start the\n'
1469 'the IFAP drug database.\n'
1470 '\n'
1471 'GNUmed will try to verify the path which may,\n'
1472 'however, fail if you are using an emulator such\n'
1473 'as Wine. Nevertheless, starting IFAP will work\n'
1474 'as long as the shell command is correct despite\n'
1475 'the failing test.'
1476 ),
1477 option = 'external.ifap-win.shell_command',
1478 bias = 'workplace',
1479 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1480 validator = is_valid
1481 )
1482
1483
1484
1533
1534
1535
1552
1555
1558
1563
1564 gmCfgWidgets.configure_string_option (
1565 message = _(
1566 'When a patient is activated GNUmed checks the\n'
1567 "proximity of the patient's birthday.\n"
1568 '\n'
1569 'If the birthday falls within the range of\n'
1570 ' "today %s <the interval you set here>"\n'
1571 'GNUmed will remind you of the recent or\n'
1572 'imminent anniversary.'
1573 ) % u'\u2213',
1574 option = u'patient_search.dob_warn_interval',
1575 bias = 'user',
1576 default_value = '1 week',
1577 validator = is_valid
1578 )
1579
1581
1582 gmCfgWidgets.configure_boolean_option (
1583 parent = self,
1584 question = _(
1585 'When adding progress notes do you want to\n'
1586 'allow opening several unassociated, new\n'
1587 'episodes for a patient at once ?\n'
1588 '\n'
1589 'This can be particularly helpful when entering\n'
1590 'progress notes on entirely new patients presenting\n'
1591 'with a multitude of problems on their first visit.'
1592 ),
1593 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1594 button_tooltips = [
1595 _('Yes, allow for multiple new episodes concurrently.'),
1596 _('No, only allow editing one new episode at a time.')
1597 ]
1598 )
1599
1601
1602 gmCfgWidgets.configure_boolean_option (
1603 parent = self,
1604 question = _(
1605 'When activating a patient, do you want GNUmed to\n'
1606 'auto-open editors for all active problems that were\n'
1607 'touched upon during the current and the most recent\n'
1608 'encounter ?'
1609 ),
1610 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1611 button_tooltips = [
1612 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1613 _('No, only auto-open one editor for a new, unassociated problem.')
1614 ]
1615 )
1616
1662
1663
1664
1667
1670
1684
1686 gmCfgWidgets.configure_boolean_option (
1687 parent = self,
1688 question = _(
1689 'Do you want GNUmed to show the encounter\n'
1690 'details editor when changing the active patient ?'
1691 ),
1692 option = 'encounter.show_editor_before_patient_change',
1693 button_tooltips = [
1694 _('Yes, show the encounter editor if it seems appropriate.'),
1695 _('No, never show the encounter editor even if it would seem useful.')
1696 ]
1697 )
1698
1703
1704 gmCfgWidgets.configure_string_option (
1705 message = _(
1706 'When a patient is activated GNUmed checks the\n'
1707 'chart for encounters lacking any entries.\n'
1708 '\n'
1709 'Any such encounters older than what you set\n'
1710 'here will be removed from the medical record.\n'
1711 '\n'
1712 'To effectively disable removal of such encounters\n'
1713 'set this option to an improbable value.\n'
1714 ),
1715 option = 'encounter.ttl_if_empty',
1716 bias = 'user',
1717 default_value = '1 week',
1718 validator = is_valid
1719 )
1720
1725
1726 gmCfgWidgets.configure_string_option (
1727 message = _(
1728 'When a patient is activated GNUmed checks the\n'
1729 'age of the most recent encounter.\n'
1730 '\n'
1731 'If that encounter is younger than this age\n'
1732 'the existing encounter will be continued.\n'
1733 '\n'
1734 '(If it is really old a new encounter is\n'
1735 ' started, or else GNUmed will ask you.)\n'
1736 ),
1737 option = 'encounter.minimum_ttl',
1738 bias = 'user',
1739 default_value = '1 hour 30 minutes',
1740 validator = is_valid
1741 )
1742
1747
1748 gmCfgWidgets.configure_string_option (
1749 message = _(
1750 'When a patient is activated GNUmed checks the\n'
1751 'age of the most recent encounter.\n'
1752 '\n'
1753 'If that encounter is older than this age\n'
1754 'GNUmed will always start a new encounter.\n'
1755 '\n'
1756 '(If it is very recent the existing encounter\n'
1757 ' is continued, or else GNUmed will ask you.)\n'
1758 ),
1759 option = 'encounter.maximum_ttl',
1760 bias = 'user',
1761 default_value = '6 hours',
1762 validator = is_valid
1763 )
1764
1773
1774 gmCfgWidgets.configure_string_option (
1775 message = _(
1776 'At any time there can only be one open (ongoing)\n'
1777 'episode for each health issue.\n'
1778 '\n'
1779 'When you try to open (add data to) an episode on a health\n'
1780 'issue GNUmed will check for an existing open episode on\n'
1781 'that issue. If there is any it will check the age of that\n'
1782 'episode. The episode is closed if it has been dormant (no\n'
1783 'data added, that is) for the period of time (in days) you\n'
1784 'set here.\n'
1785 '\n'
1786 "If the existing episode hasn't been dormant long enough\n"
1787 'GNUmed will consult you what to do.\n'
1788 '\n'
1789 'Enter maximum episode dormancy in DAYS:'
1790 ),
1791 option = 'episode.ttl',
1792 bias = 'user',
1793 default_value = 60,
1794 validator = is_valid
1795 )
1796
1827
1842
1867
1879
1880 gmCfgWidgets.configure_string_option (
1881 message = _(
1882 'GNUmed can check for new releases being available. To do\n'
1883 'so it needs to load version information from an URL.\n'
1884 '\n'
1885 'The default URL is:\n'
1886 '\n'
1887 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1888 '\n'
1889 'but you can configure any other URL locally. Note\n'
1890 'that you must enter the location as a valid URL.\n'
1891 'Depending on the URL the client will need online\n'
1892 'access when checking for updates.'
1893 ),
1894 option = u'horstspace.update.url',
1895 bias = u'workplace',
1896 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1897 validator = is_valid
1898 )
1899
1917
1934
1951
1962
1963 gmCfgWidgets.configure_string_option (
1964 message = _(
1965 'GNUmed can show the document review dialog after\n'
1966 'calling the appropriate viewer for that document.\n'
1967 '\n'
1968 'Select the conditions under which you want\n'
1969 'GNUmed to do so:\n'
1970 '\n'
1971 ' 0: never display the review dialog\n'
1972 ' 1: always display the dialog\n'
1973 ' 2: only if there is no previous review by me\n'
1974 ' 3: only if there is no previous review at all\n'
1975 ' 4: only if there is no review by the responsible reviewer\n'
1976 '\n'
1977 'Note that if a viewer is configured to not block\n'
1978 'GNUmed during document display the review dialog\n'
1979 'will actually appear in parallel to the viewer.'
1980 ),
1981 option = u'horstspace.document_viewer.review_after_display',
1982 bias = u'user',
1983 default_value = 3,
1984 validator = is_valid
1985 )
1986
1988
1989
1990 master_data_lists = [
1991 'adr',
1992 'drugs',
1993 'codes',
1994 'communication_channel_types',
1995 'substances_in_brands',
1996 'substances',
1997 'labs',
1998 'form_templates',
1999 'doc_types',
2000 'enc_types',
2001 'text_expansions',
2002 'meta_test_types',
2003 'orgs',
2004 'patient_tags',
2005 'provinces',
2006 'db_translations',
2007 'test_types',
2008 'vacc_indications',
2009 'vaccines',
2010 'workplaces'
2011 ]
2012
2013 master_data_list_names = {
2014 'adr': _('Addresses (likely slow)'),
2015 'drugs': _('Branded drugs (as marketed)'),
2016 'codes': _('Codes and their respective terms'),
2017 'communication_channel_types': _('Communication channel types'),
2018 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2019 'labs': _('Diagnostic organizations (path labs, ...)'),
2020 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2021 'doc_types': _('Document types'),
2022 'enc_types': _('Encounter types'),
2023 'text_expansions': _('Keyword based text expansion macros'),
2024 'meta_test_types': _('Meta test/measurement types'),
2025 'orgs': _('Organizations with their units, addresses, and comm channels'),
2026 'patient_tags': _('Patient tags'),
2027 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2028 'db_translations': _('String translations in the database'),
2029 'test_types': _('Test/measurement types'),
2030 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2031 'vaccines': _('Vaccines'),
2032 'workplaces': _('Workplace profiles (which plugins to load)'),
2033 'substances': _('Consumable substances')
2034 }
2035
2036 map_list2handler = {
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': gmAddressWidgets.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 'orgs': gmOrganizationWidgets.manage_orgs,
2053 'adr': gmAddressWidgets.manage_addresses,
2054 'substances': gmMedicationWidgets.manage_consumable_substances,
2055 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2056 'communication_channel_types': gmContactWidgets.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
2415
2425
2433
2441
2449
2457
2466
2475
2483
2500
2503
2506
2508
2509 pat = gmPerson.gmCurrentPatient()
2510 if not pat.connected:
2511 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2512 return False
2513
2514 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2515
2516 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2517 gmTools.mkdir(aDefDir)
2518
2519 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2520 dlg = wx.FileDialog (
2521 parent = self,
2522 message = _("Save patient's EMR journal as..."),
2523 defaultDir = aDefDir,
2524 defaultFile = fname,
2525 wildcard = aWildcard,
2526 style = wx.SAVE
2527 )
2528 choice = dlg.ShowModal()
2529 fname = dlg.GetPath()
2530 dlg.Destroy()
2531 if choice != wx.ID_OK:
2532 return True
2533
2534 _log.debug('exporting EMR journal to [%s]' % fname)
2535
2536 exporter = gmPatientExporter.cEMRJournalExporter()
2537
2538 wx.BeginBusyCursor()
2539 try:
2540 fname = exporter.export_to_file(filename = fname)
2541 except:
2542 wx.EndBusyCursor()
2543 gmGuiHelpers.gm_show_error (
2544 _('Error exporting patient EMR as chronological journal.'),
2545 _('EMR journal export')
2546 )
2547 raise
2548 wx.EndBusyCursor()
2549
2550 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2551
2552 return True
2553
2560
2562 curr_pat = gmPerson.gmCurrentPatient()
2563 if not curr_pat.connected:
2564 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2565 return
2566
2567 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2568 if tag is None:
2569 return
2570
2571 tag = curr_pat.add_tag(tag['pk_tag_image'])
2572 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2573 comment = wx.GetTextFromUser (
2574 message = msg,
2575 caption = _('Editing tag comment'),
2576 default_value = gmTools.coalesce(tag['comment'], u''),
2577 parent = self
2578 )
2579
2580 if comment == u'':
2581 return
2582
2583 if comment.strip() == tag['comment']:
2584 return
2585
2586 if comment == u' ':
2587 tag['comment'] = None
2588 else:
2589 tag['comment'] = comment.strip()
2590
2591 tag.save()
2592
2602
2604 curr_pat = gmPerson.gmCurrentPatient()
2605 if not curr_pat.connected:
2606 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2607 return False
2608
2609 enc = 'cp850'
2610 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2611 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2612 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2613
2616
2624
2632
2635
2642
2646
2649
2650
2651
2652
2655
2658
2663
2665 """Cleanup helper.
2666
2667 - should ALWAYS be called when this program is
2668 to be terminated
2669 - ANY code that should be executed before a
2670 regular shutdown should go in here
2671 - framework still functional
2672 """
2673 _log.debug('gmTopLevelFrame._clean_exit() start')
2674
2675
2676 listener = gmBackendListener.gmBackendListener()
2677 try:
2678 listener.shutdown()
2679 except:
2680 _log.exception('cannot stop backend notifications listener thread')
2681
2682
2683 if _scripting_listener is not None:
2684 try:
2685 _scripting_listener.shutdown()
2686 except:
2687 _log.exception('cannot stop scripting listener thread')
2688
2689
2690 self.clock_update_timer.Stop()
2691 gmTimer.shutdown()
2692 gmPhraseWheel.shutdown()
2693
2694
2695 for call_back in self.__pre_exit_callbacks:
2696 try:
2697 call_back()
2698 except:
2699 print "*** pre-exit callback failed ***"
2700 print call_back
2701 _log.exception('callback [%s] failed', call_back)
2702
2703
2704 gmDispatcher.send(u'application_closing')
2705
2706
2707 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2708
2709
2710 curr_width, curr_height = self.GetClientSizeTuple()
2711 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2712 dbcfg = gmCfg.cCfgSQL()
2713 dbcfg.set (
2714 option = 'main.window.width',
2715 value = curr_width,
2716 workplace = gmSurgery.gmCurrentPractice().active_workplace
2717 )
2718 dbcfg.set (
2719 option = 'main.window.height',
2720 value = curr_height,
2721 workplace = gmSurgery.gmCurrentPractice().active_workplace
2722 )
2723
2724 if _cfg.get(option = 'debug'):
2725 print '---=== GNUmed shutdown ===---'
2726 try:
2727 print _('You have to manually close this window to finalize shutting down GNUmed.')
2728 print _('This is so that you can inspect the console output at your leisure.')
2729 except UnicodeEncodeError:
2730 print 'You have to manually close this window to finalize shutting down GNUmed.'
2731 print 'This is so that you can inspect the console output at your leisure.'
2732 print '---=== GNUmed shutdown ===---'
2733
2734
2735 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2736
2737
2738 import threading
2739 _log.debug("%s active threads", threading.activeCount())
2740 for t in threading.enumerate():
2741 _log.debug('thread %s', t)
2742
2743 _log.debug('gmTopLevelFrame._clean_exit() end')
2744
2745
2746
2748
2749 if _cfg.get(option = 'slave'):
2750 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2751 _cfg.get(option = 'slave personality'),
2752 _cfg.get(option = 'xml-rpc port')
2753 )
2754 else:
2755 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2756
2758 """Update title of main window based on template.
2759
2760 This gives nice tooltips on iconified GNUmed instances.
2761
2762 User research indicates that in the title bar people want
2763 the date of birth, not the age, so please stick to this
2764 convention.
2765 """
2766 args = {}
2767
2768 pat = gmPerson.gmCurrentPatient()
2769 if pat.connected:
2770 args['pat'] = u'%s %s %s (%s) #%d' % (
2771 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2772 pat['firstnames'],
2773 pat['lastnames'],
2774 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2775 pat['pk_identity']
2776 )
2777 else:
2778 args['pat'] = _('no patient')
2779
2780 args['prov'] = u'%s%s.%s' % (
2781 gmTools.coalesce(_provider['title'], u'', u'%s '),
2782 _provider['firstnames'][:1],
2783 _provider['lastnames']
2784 )
2785
2786 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2787
2788 self.SetTitle(self.__title_template % args)
2789
2790
2792 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2793 sb.SetStatusWidths([-1, 225])
2794
2795 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2796 self._cb_update_clock()
2797
2798 self.clock_update_timer.Start(milliseconds = 1000)
2799
2801 """Displays date and local time in the second slot of the status bar"""
2802 t = time.localtime(time.time())
2803 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2804 self.SetStatusText(st,1)
2805
2807 """Lock GNUmed client against unauthorized access"""
2808
2809
2810
2811 return
2812
2814 """Unlock the main notebook widgets
2815 As long as we are not logged into the database backend,
2816 all pages but the 'login' page of the main notebook widget
2817 are locked; i.e. not accessible by the user
2818 """
2819
2820
2821
2822
2823
2824 return
2825
2827 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2828
2830
2832
2833 self.__starting_up = True
2834
2835 gmExceptionHandlingWidgets.install_wx_exception_handler()
2836 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2837
2838
2839
2840
2841 self.SetAppName(u'gnumed')
2842 self.SetVendorName(u'The GNUmed Development Community.')
2843 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2844 paths.init_paths(wx = wx, app_name = u'gnumed')
2845
2846 if not self.__setup_prefs_file():
2847 return False
2848
2849 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2850
2851 self.__guibroker = gmGuiBroker.GuiBroker()
2852 self.__setup_platform()
2853
2854 if not self.__establish_backend_connection():
2855 return False
2856
2857 if not _cfg.get(option = 'skip-update-check'):
2858 self.__check_for_updates()
2859
2860 if _cfg.get(option = 'slave'):
2861 if not self.__setup_scripting_listener():
2862 return False
2863
2864
2865 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2866 frame.CentreOnScreen(wx.BOTH)
2867 self.SetTopWindow(frame)
2868 frame.Show(True)
2869
2870 if _cfg.get(option = 'debug'):
2871 self.RedirectStdio()
2872 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2873
2874
2875 print '---=== GNUmed startup ===---'
2876 print _('redirecting STDOUT/STDERR to this log window')
2877 print '---=== GNUmed startup ===---'
2878
2879 self.__setup_user_activity_timer()
2880 self.__register_events()
2881
2882 wx.CallAfter(self._do_after_init)
2883
2884 return True
2885
2887 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2888
2889 - after destroying all application windows and controls
2890 - before wx.Windows internal cleanup
2891 """
2892 _log.debug('gmApp.OnExit() start')
2893
2894 self.__shutdown_user_activity_timer()
2895
2896 if _cfg.get(option = 'debug'):
2897 self.RestoreStdio()
2898 sys.stdin = sys.__stdin__
2899 sys.stdout = sys.__stdout__
2900 sys.stderr = sys.__stderr__
2901
2902 _log.debug('gmApp.OnExit() end')
2903
2905 wx.Bell()
2906 wx.Bell()
2907 wx.Bell()
2908 _log.warning('unhandled event detected: QUERY_END_SESSION')
2909 _log.info('we should be saving ourselves from here')
2910 gmLog2.flush()
2911 print "unhandled event detected: QUERY_END_SESSION"
2912
2914 wx.Bell()
2915 wx.Bell()
2916 wx.Bell()
2917 _log.warning('unhandled event detected: END_SESSION')
2918 gmLog2.flush()
2919 print "unhandled event detected: END_SESSION"
2920
2931
2933 self.user_activity_detected = True
2934 evt.Skip()
2935
2937
2938 if self.user_activity_detected:
2939 self.elapsed_inactivity_slices = 0
2940 self.user_activity_detected = False
2941 self.elapsed_inactivity_slices += 1
2942 else:
2943 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2944
2945 pass
2946
2947 self.user_activity_timer.Start(oneShot = True)
2948
2949
2950
2952 try:
2953 kwargs['originated_in_database']
2954 print '==> got notification from database "%s":' % kwargs['signal']
2955 except KeyError:
2956 print '==> received signal from client: "%s"' % kwargs['signal']
2957
2958 del kwargs['signal']
2959 for key in kwargs.keys():
2960 print ' [%s]: %s' % (key, kwargs[key])
2961
2963 print "wx.lib.pubsub message:"
2964 print msg.topic
2965 print msg.data
2966
2972
2974 self.user_activity_detected = True
2975 self.elapsed_inactivity_slices = 0
2976
2977 self.max_user_inactivity_slices = 15
2978 self.user_activity_timer = gmTimer.cTimer (
2979 callback = self._on_user_activity_timer_expired,
2980 delay = 2000
2981 )
2982 self.user_activity_timer.Start(oneShot=True)
2983
2985 try:
2986 self.user_activity_timer.Stop()
2987 del self.user_activity_timer
2988 except:
2989 pass
2990
2992 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2993 wx.EVT_END_SESSION(self, self._on_end_session)
2994
2995
2996
2997
2998
2999 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
3000
3001 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
3002 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
3003
3004 if _cfg.get(option = 'debug'):
3005 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
3006 _log.debug('connected old signal monitor')
3007 wx.lib.pubsub.Publisher().subscribe(listener = self._signal_debugging_monitor_pubsub)
3008 _log.debug('connected wx.lib.pubsub based signal monitor for all topics')
3009
3010
3011
3012
3013
3014
3030
3032 """Handle all the database related tasks necessary for startup."""
3033
3034
3035 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
3036
3037 from Gnumed.wxpython import gmAuthWidgets
3038 connected = gmAuthWidgets.connect_to_database (
3039 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
3040 require_version = not override
3041 )
3042 if not connected:
3043 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
3044 return False
3045
3046
3047 try:
3048 global _provider
3049 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
3050 except ValueError:
3051 account = gmPG2.get_current_user()
3052 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
3053 msg = _(
3054 'The database account [%s] cannot be used as a\n'
3055 'staff member login for GNUmed. There was an\n'
3056 'error retrieving staff details for it.\n\n'
3057 'Please ask your administrator for help.\n'
3058 ) % account
3059 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
3060 return False
3061
3062
3063 tmp = '%s%s %s (%s = %s)' % (
3064 gmTools.coalesce(_provider['title'], ''),
3065 _provider['firstnames'],
3066 _provider['lastnames'],
3067 _provider['short_alias'],
3068 _provider['db_user']
3069 )
3070 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
3071
3072
3073 surgery = gmSurgery.gmCurrentPractice()
3074 msg = surgery.db_logon_banner
3075 if msg.strip() != u'':
3076
3077 login = gmPG2.get_default_login()
3078 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
3079 login.database,
3080 gmTools.coalesce(login.host, u'localhost')
3081 ))
3082 msg = auth + msg + u'\n\n'
3083
3084 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3085 None,
3086
3087 -1,
3088 caption = _('Verifying database'),
3089 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3090 button_defs = [
3091 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3092 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3093 ]
3094 )
3095 go_on = dlg.ShowModal()
3096 dlg.Destroy()
3097 if go_on != wx.ID_YES:
3098 _log.info('user decided to not connect to this database')
3099 return False
3100
3101
3102 self.__check_db_lang()
3103
3104 return True
3105
3107 """Setup access to a config file for storing preferences."""
3108
3109 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3110
3111 candidates = []
3112 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3113 if explicit_file is not None:
3114 candidates.append(explicit_file)
3115
3116 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3117 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3118 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3119
3120 prefs_file = None
3121 for candidate in candidates:
3122 try:
3123 open(candidate, 'a+').close()
3124 prefs_file = candidate
3125 break
3126 except IOError:
3127 continue
3128
3129 if prefs_file is None:
3130 msg = _(
3131 'Cannot find configuration file in any of:\n'
3132 '\n'
3133 ' %s\n'
3134 'You may need to use the comand line option\n'
3135 '\n'
3136 ' --conf-file=<FILE>'
3137 ) % '\n '.join(candidates)
3138 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3139 return False
3140
3141 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3142 _log.info('user preferences file: %s', prefs_file)
3143
3144 return True
3145
3147
3148 from socket import error as SocketError
3149 from Gnumed.pycommon import gmScriptingListener
3150 from Gnumed.wxpython import gmMacro
3151
3152 slave_personality = gmTools.coalesce (
3153 _cfg.get (
3154 group = u'workplace',
3155 option = u'slave personality',
3156 source_order = [
3157 ('explicit', 'return'),
3158 ('workbase', 'return'),
3159 ('user', 'return'),
3160 ('system', 'return')
3161 ]
3162 ),
3163 u'gnumed-client'
3164 )
3165 _cfg.set_option(option = 'slave personality', value = slave_personality)
3166
3167
3168 port = int (
3169 gmTools.coalesce (
3170 _cfg.get (
3171 group = u'workplace',
3172 option = u'xml-rpc port',
3173 source_order = [
3174 ('explicit', 'return'),
3175 ('workbase', 'return'),
3176 ('user', 'return'),
3177 ('system', 'return')
3178 ]
3179 ),
3180 9999
3181 )
3182 )
3183 _cfg.set_option(option = 'xml-rpc port', value = port)
3184
3185 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3186 global _scripting_listener
3187 try:
3188 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3189 except SocketError, e:
3190 _log.exception('cannot start GNUmed XML-RPC server')
3191 gmGuiHelpers.gm_show_error (
3192 aMessage = (
3193 'Cannot start the GNUmed server:\n'
3194 '\n'
3195 ' [%s]'
3196 ) % e,
3197 aTitle = _('GNUmed startup')
3198 )
3199 return False
3200
3201 return True
3202
3223
3225 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3226 _log.warning("system locale is undefined (probably meaning 'C')")
3227 return True
3228
3229
3230 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3231 db_lang = rows[0]['lang']
3232
3233 if db_lang is None:
3234 _log.debug("database locale currently not set")
3235 msg = _(
3236 "There is no language selected in the database for user [%s].\n"
3237 "Your system language is currently set to [%s].\n\n"
3238 "Do you want to set the database language to '%s' ?\n\n"
3239 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3240 checkbox_msg = _('Remember to ignore missing language')
3241 else:
3242 _log.debug("current database locale: [%s]" % db_lang)
3243 msg = _(
3244 "The currently selected database language ('%s') does\n"
3245 "not match the current system language ('%s').\n"
3246 "\n"
3247 "Do you want to set the database language to '%s' ?\n"
3248 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3249 checkbox_msg = _('Remember to ignore language mismatch')
3250
3251
3252 if db_lang == gmI18N.system_locale_level['full']:
3253 _log.debug('Database locale (%s) up to date.' % db_lang)
3254 return True
3255 if db_lang == gmI18N.system_locale_level['country']:
3256 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3257 return True
3258 if db_lang == gmI18N.system_locale_level['language']:
3259 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3260 return True
3261
3262 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3263
3264
3265 ignored_sys_lang = _cfg.get (
3266 group = u'backend',
3267 option = u'ignored mismatching system locale',
3268 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3269 )
3270
3271
3272 if gmI18N.system_locale == ignored_sys_lang:
3273 _log.info('configured to ignore system-to-database locale mismatch')
3274 return True
3275
3276
3277 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3278 None,
3279 -1,
3280 caption = _('Checking database language settings'),
3281 question = msg,
3282 button_defs = [
3283 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3284 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3285 ],
3286 show_checkbox = True,
3287 checkbox_msg = checkbox_msg,
3288 checkbox_tooltip = _(
3289 'Checking this will make GNUmed remember your decision\n'
3290 'until the system language is changed.\n'
3291 '\n'
3292 'You can also reactivate this inquiry by removing the\n'
3293 'corresponding "ignore" option from the configuration file\n'
3294 '\n'
3295 ' [%s]'
3296 ) % _cfg.get(option = 'user_preferences_file')
3297 )
3298 decision = dlg.ShowModal()
3299 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3300 dlg.Destroy()
3301
3302 if decision == wx.ID_NO:
3303 if not remember_ignoring_problem:
3304 return True
3305 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3306 gmCfg2.set_option_in_INI_file (
3307 filename = _cfg.get(option = 'user_preferences_file'),
3308 group = 'backend',
3309 option = 'ignored mismatching system locale',
3310 value = gmI18N.system_locale
3311 )
3312 return True
3313
3314
3315 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3316 if len(lang) > 0:
3317
3318
3319 rows, idx = gmPG2.run_rw_queries (
3320 link_obj = None,
3321 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3322 return_data = True
3323 )
3324 if rows[0][0]:
3325 _log.debug("Successfully set database language to [%s]." % lang)
3326 else:
3327 _log.error('Cannot set database language to [%s].' % lang)
3328 continue
3329 return True
3330
3331
3332 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3333 gmPG2.run_rw_queries(queries = [{
3334 'cmd': u'select i18n.force_curr_lang(%s)',
3335 'args': [gmI18N.system_locale_level['country']]
3336 }])
3337
3338 return True
3339
3341 try:
3342 kwargs['originated_in_database']
3343 print '==> got notification from database "%s":' % kwargs['signal']
3344 except KeyError:
3345 print '==> received signal from client: "%s"' % kwargs['signal']
3346
3347 del kwargs['signal']
3348 for key in kwargs.keys():
3349
3350 try: print ' [%s]: %s' % (key, kwargs[key])
3351 except: print 'cannot print signal information'
3352
3354
3355 try:
3356 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3357 print ' data: %s' % msg.data
3358 print msg
3359 except: print 'problem printing pubsub message information'
3360
3362
3363 if _cfg.get(option = 'debug'):
3364 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3365 _log.debug('gmDispatcher signal monitor activated')
3366 wx.lib.pubsub.Publisher().subscribe (
3367 listener = _signal_debugging_monitor_pubsub
3368
3369 )
3370 _log.debug('wx.lib.pubsub signal monitor activated')
3371
3372 wx.InitAllImageHandlers()
3373
3374
3375
3376 app = gmApp(redirect = False, clearSigInt = False)
3377 app.MainLoop()
3378
3379
3380
3381 if __name__ == '__main__':
3382
3383 from GNUmed.pycommon import gmI18N
3384 gmI18N.activate_locale()
3385 gmI18N.install_domain()
3386
3387 _log.info('Starting up as main module.')
3388 main()
3389
3390
3391