1
2 """GNUmed GUI client.
3
4 This contains the GUI application framework and main window
5 of the all signing all dancing GNUmed Python Reference
6 client. It relies on the <gnumed.py> launcher having set up
7 the non-GUI-related runtime environment.
8
9 This source code is protected by the GPL licensing scheme.
10 Details regarding the GPL are available at http://www.gnu.org
11 You may use and share it as long as you don't deny this right
12 to anybody else.
13
14 copyright: authors
15 """
16
17 __version__ = "$Revision: 1.491 $"
18 __author__ = "H. Herb <hherb@gnumed.net>,\
19 K. Hilbert <Karsten.Hilbert@gmx.net>,\
20 I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
21 __license__ = 'GPL (details at http://www.gnu.org)'
22
23
24 import sys, time, os, locale, os.path, datetime as pyDT
25 import webbrowser, shutil, logging, urllib2, subprocess, glob
26
27
28
29
30 if not hasattr(sys, 'frozen'):
31 import wxversion
32 wxversion.ensureMinimal('2.8-unicode', optionsRequired=True)
33
34 try:
35 import wx
36 import wx.lib.pubsub
37 except ImportError:
38 print "GNUmed startup: Cannot import wxPython library."
39 print "GNUmed startup: Make sure wxPython is installed."
40 print 'CRITICAL ERROR: Error importing wxPython. Halted.'
41 raise
42
43
44
45 version = int(u'%s%s' % (wx.MAJOR_VERSION, wx.MINOR_VERSION))
46 if (version < 28) or ('unicode' not in wx.PlatformInfo):
47 print "GNUmed startup: Unsupported wxPython version (%s: %s)." % (wx.VERSION_STRING, wx.PlatformInfo)
48 print "GNUmed startup: wxPython 2.8+ with unicode support is required."
49 print 'CRITICAL ERROR: Proper wxPython version not found. Halted.'
50 raise ValueError('wxPython 2.8+ with unicode support not found')
51
52
53
54 from Gnumed.pycommon import gmCfg, gmPG2, gmDispatcher, gmGuiBroker, gmI18N
55 from Gnumed.pycommon import gmExceptions, gmShellAPI, gmTools, gmDateTime
56 from Gnumed.pycommon import gmHooks, gmBackendListener, gmCfg2, gmLog2
57
58 from Gnumed.business import gmPerson, gmClinicalRecord, gmSurgery, gmEMRStructItems
59
60 from Gnumed.exporters import gmPatientExporter
61
62 from Gnumed.wxpython import gmGuiHelpers, gmHorstSpace, gmEMRBrowser
63 from Gnumed.wxpython import gmDemographicsWidgets, gmEMRStructWidgets
64 from Gnumed.wxpython import gmPatSearchWidgets, gmAllergyWidgets, gmListWidgets
65 from Gnumed.wxpython import gmProviderInboxWidgets, gmCfgWidgets, gmExceptionHandlingWidgets
66 from Gnumed.wxpython import gmNarrativeWidgets, gmPhraseWheel, gmMedicationWidgets
67 from Gnumed.wxpython import gmStaffWidgets, gmDocumentWidgets, gmTimer, gmMeasurementWidgets
68 from Gnumed.wxpython import gmFormWidgets, gmSnellen, gmVaccWidgets
69
70 try:
71 _('dummy-no-need-to-translate-but-make-epydoc-happy')
72 except NameError:
73 _ = lambda x:x
74
75 _cfg = gmCfg2.gmCfgData()
76 _provider = None
77 _scripting_listener = None
78
79 _log = logging.getLogger('gm.main')
80 _log.info(__version__)
81 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
82
83
85 """GNUmed client's main windows frame.
86
87 This is where it all happens. Avoid popping up any other windows.
88 Most user interaction should happen to and from widgets within this frame
89 """
90
91 - def __init__(self, parent, id, title, size=wx.DefaultSize):
92 """You'll have to browse the source to understand what the constructor does
93 """
94 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
95
96 self.__gb = gmGuiBroker.GuiBroker()
97 self.__pre_exit_callbacks = []
98 self.bar_width = -1
99 self.menu_id2plugin = {}
100
101 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
102
103 self.__setup_main_menu()
104 self.setup_statusbar()
105 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
106 gmTools.coalesce(_provider['title'], ''),
107 _provider['firstnames'][:1],
108 _provider['lastnames'],
109 _provider['short_alias'],
110 _provider['db_user']
111 ))
112
113 self.__set_window_title_template()
114 self.__update_window_title()
115
116
117
118
119
120 self.SetIcon(gmTools.get_icon(wx = wx))
121
122 self.__register_events()
123
124 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
125 self.vbox = wx.BoxSizer(wx.VERTICAL)
126 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
127
128 self.SetAutoLayout(True)
129 self.SetSizerAndFit(self.vbox)
130
131
132
133
134
135 self.__set_GUI_size()
136
138 """Try to get previous window size from backend."""
139
140 cfg = gmCfg.cCfgSQL()
141
142
143 width = int(cfg.get2 (
144 option = 'main.window.width',
145 workplace = gmSurgery.gmCurrentPractice().active_workplace,
146 bias = 'workplace',
147 default = 800
148 ))
149
150
151 height = int(cfg.get2 (
152 option = 'main.window.height',
153 workplace = gmSurgery.gmCurrentPractice().active_workplace,
154 bias = 'workplace',
155 default = 600
156 ))
157
158 dw = wx.DisplaySize()[0]
159 dh = wx.DisplaySize()[1]
160
161 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
162 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
163 _log.debug('previous GUI size [%s:%s]', width, height)
164
165
166 if width > dw:
167 _log.debug('adjusting GUI width from %s to %s', width, dw)
168 width = dw
169
170 if height > dh:
171 _log.debug('adjusting GUI height from %s to %s', height, dh)
172 height = dh
173
174
175 if width < 100:
176 _log.debug('adjusting GUI width to minimum of 100 pixel')
177 width = 100
178 if height < 100:
179 _log.debug('adjusting GUI height to minimum of 100 pixel')
180 height = 100
181
182 _log.info('setting GUI to size [%s:%s]', width, height)
183
184 self.SetClientSize(wx.Size(width, height))
185
187 """Create the main menu entries.
188
189 Individual entries are farmed out to the modules.
190 """
191 global wx
192 self.mainmenu = wx.MenuBar()
193 self.__gb['main.mainmenu'] = self.mainmenu
194
195
196 menu_gnumed = wx.Menu()
197
198 self.menu_plugins = wx.Menu()
199 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
200
201 ID = wx.NewId()
202 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
203 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
204
205 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
206 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
207
208
209 menu_gnumed.AppendSeparator()
210
211
212 menu_config = wx.Menu()
213 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
214
215 item = menu_config.Append(-1, _('List configuration'), _('List all configuration items stored in the database.'))
216 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
217
218
219 menu_cfg_db = wx.Menu()
220 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
221
222 ID = wx.NewId()
223 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
224 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
225
226 ID = wx.NewId()
227 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
228 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
229
230
231 menu_cfg_client = wx.Menu()
232 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
233
234 ID = wx.NewId()
235 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
236 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
237
238 ID = wx.NewId()
239 menu_cfg_client.Append(ID, _('Temporary directory'), _('Configure the directory to use as scratch space for temporary files.'))
240 wx.EVT_MENU(self, ID, self.__on_configure_temp_dir)
241
242 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
243 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
244
245
246 menu_cfg_ui = wx.Menu()
247 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
248
249
250 menu_cfg_doc = wx.Menu()
251 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
252
253 ID = wx.NewId()
254 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
255 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
256
257 ID = wx.NewId()
258 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
259 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
260
261 ID = wx.NewId()
262 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
263 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
264
265
266 menu_cfg_update = wx.Menu()
267 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
268
269 ID = wx.NewId()
270 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
271 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
272
273 ID = wx.NewId()
274 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
275 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
276
277 ID = wx.NewId()
278 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
279 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
280
281
282 menu_cfg_pat_search = wx.Menu()
283 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
284
285 ID = wx.NewId()
286 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
287 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
288
289 ID = wx.NewId()
290 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
291 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
292
293 ID = wx.NewId()
294 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
295 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
296
297 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
298 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
299
300 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
301 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
302
303
304 menu_cfg_soap_editing = wx.Menu()
305 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
306
307 ID = wx.NewId()
308 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
309 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
310
311
312 menu_cfg_ext_tools = wx.Menu()
313 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
314
315
316
317
318
319 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
320 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
321
322 ID = wx.NewId()
323 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
324 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
325
326 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
327 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
328
329 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
330 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
331
332 item = menu_cfg_ext_tools.Append(-1, _('FreeDiams path'), _('Set the path for the FreeDiams binary.'))
333 self.Bind(wx.EVT_MENU, self.__on_configure_freediams_cmd, item)
334
335 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
336 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
337
338
339 menu_cfg_emr = wx.Menu()
340 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
341
342 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
343 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
344
345
346 menu_cfg_encounter = wx.Menu()
347 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
348
349 ID = wx.NewId()
350 menu_cfg_encounter.Append(ID, _('Edit on patient change'), _('Edit encounter details on changing of patients.'))
351 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
352
353 ID = wx.NewId()
354 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
355 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
356
357 ID = wx.NewId()
358 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
359 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
360
361 ID = wx.NewId()
362 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
363 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
364
365 ID = wx.NewId()
366 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
367 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
368
369
370 menu_cfg_episode = wx.Menu()
371 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
372
373 ID = wx.NewId()
374 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
375 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
376
377
378 menu_master_data = wx.Menu()
379 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
380
381 item = menu_master_data.Append(-1, _('Workplace profiles'), _('Manage the plugins to load per workplace.'))
382 self.Bind(wx.EVT_MENU, self.__on_configure_workplace, item)
383
384 menu_master_data.AppendSeparator()
385
386 item = menu_master_data.Append(-1, _('&Document types'), _('Manage the document types available in the system.'))
387 self.Bind(wx.EVT_MENU, self.__on_edit_doc_types, item)
388
389 item = menu_master_data.Append(-1, _('&Form templates'), _('Manage templates for forms and letters.'))
390 self.Bind(wx.EVT_MENU, self.__on_manage_form_templates, item)
391
392 item = menu_master_data.Append(-1, _('&Text expansions'), _('Manage keyword based text expansion macros.'))
393 self.Bind(wx.EVT_MENU, self.__on_manage_text_expansion, item)
394
395 menu_master_data.AppendSeparator()
396
397 item = menu_master_data.Append(-1, _('&Encounter types'), _('Manage encounter types.'))
398 self.Bind(wx.EVT_MENU, self.__on_manage_encounter_types, item)
399
400 item = menu_master_data.Append(-1, _('&Provinces'), _('Manage provinces (counties, territories, ...).'))
401 self.Bind(wx.EVT_MENU, self.__on_manage_provinces, item)
402
403 menu_master_data.AppendSeparator()
404
405 item = menu_master_data.Append(-1, _('Substances'), _('Manage substances in use.'))
406 self.Bind(wx.EVT_MENU, self.__on_manage_substances, item)
407
408 item = menu_master_data.Append(-1, _('Drugs'), _('Manage branded drugs.'))
409 self.Bind(wx.EVT_MENU, self.__on_manage_branded_drugs, item)
410
411 item = menu_master_data.Append(-1, _('Drug components'), _('Manage components of branded drugs.'))
412 self.Bind(wx.EVT_MENU, self.__on_manage_substances_in_brands, item)
413
414 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
415 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
416
417 menu_master_data.AppendSeparator()
418
419 item = menu_master_data.Append(-1, _('Diagnostic orgs'), _('Manage diagnostic organisations (path labs etc).'))
420 self.Bind(wx.EVT_MENU, self.__on_manage_test_orgs, item)
421
422 item = menu_master_data.Append(-1, _('&Test types'), _('Manage test/measurement types.'))
423 self.Bind(wx.EVT_MENU, self.__on_manage_test_types, item)
424
425 item = menu_master_data.Append(-1, _('&Meta test types'), _('Show meta test/measurement types.'))
426 self.Bind(wx.EVT_MENU, self.__on_manage_meta_test_types, item)
427
428 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
429 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
430
431 menu_master_data.AppendSeparator()
432
433 item = menu_master_data.Append(-1, _('Vaccines'), _('Show known vaccines.'))
434 self.Bind(wx.EVT_MENU, self.__on_manage_vaccines, item)
435
436
437 menu_users = wx.Menu()
438 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
439
440 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
441 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
442
443 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
444 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
445
446
447 menu_gnumed.AppendSeparator()
448
449 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
450 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
451
452 self.mainmenu.Append(menu_gnumed, '&GNUmed')
453
454
455 menu_patient = wx.Menu()
456
457 ID_CREATE_PATIENT = wx.NewId()
458 menu_patient.Append(ID_CREATE_PATIENT, _('Register person'), _("Register a new person with GNUmed"))
459 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
460
461
462
463
464 ID_LOAD_EXT_PAT = wx.NewId()
465 menu_patient.Append(ID_LOAD_EXT_PAT, _('Load external'), _('Load and possibly create person from an external source.'))
466 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
467
468 ID_DEL_PAT = wx.NewId()
469 menu_patient.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
470 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
471
472 item = menu_patient.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
473 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
474
475 menu_patient.AppendSeparator()
476
477 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
478 menu_patient.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
479 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
480
481
482 ID = wx.NewId()
483 menu_patient.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
484 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
485
486 menu_patient.AppendSeparator()
487
488 self.mainmenu.Append(menu_patient, '&Person')
489 self.__gb['main.patientmenu'] = menu_patient
490
491
492 menu_emr = wx.Menu()
493 self.mainmenu.Append(menu_emr, _("&EMR"))
494 self.__gb['main.emrmenu'] = menu_emr
495
496
497 menu_emr_show = wx.Menu()
498 menu_emr.AppendMenu(wx.NewId(), _('Show as ...'), menu_emr_show)
499 self.__gb['main.emr_showmenu'] = menu_emr_show
500
501
502 item = menu_emr_show.Append(-1, _('Summary'), _('Show a high-level summary of the EMR.'))
503 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
504
505
506 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
507 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
508
509 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
510 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
511
512
513 menu_emr_edit = wx.Menu()
514 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
515
516 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'))
517 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
518
519 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
520 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
521
522 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
523 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
524
525 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
526 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
527
528 item = menu_emr_edit.Append(-1, _('&Hospital stays'), _('Manage hospital stays.'))
529 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
530
531 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
532 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
533
534 item = menu_emr_edit.Append(-1, _('&Measurement(s)'), _('Add (a) measurement result(s) for the current patient.'))
535 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
536
537
538
539
540
541
542
543 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
544 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
545
546
547 item = menu_emr.Append(-1, _('&Encounters list'), _('List all encounters including empty ones.'))
548 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
549
550
551 menu_emr.AppendSeparator()
552
553 menu_emr_export = wx.Menu()
554 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
555
556 ID_EXPORT_EMR_ASCII = wx.NewId()
557 menu_emr_export.Append (
558 ID_EXPORT_EMR_ASCII,
559 _('Text document'),
560 _("Export the EMR of the active patient into a text file")
561 )
562 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
563
564 ID_EXPORT_EMR_JOURNAL = wx.NewId()
565 menu_emr_export.Append (
566 ID_EXPORT_EMR_JOURNAL,
567 _('Journal'),
568 _("Export the EMR of the active patient as a chronological journal into a text file")
569 )
570 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
571
572 ID_EXPORT_MEDISTAR = wx.NewId()
573 menu_emr_export.Append (
574 ID_EXPORT_MEDISTAR,
575 _('MEDISTAR import format'),
576 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
577 )
578 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
579
580
581 menu_emr.AppendSeparator()
582
583
584 menu_paperwork = wx.Menu()
585
586 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
587 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
588
589 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
590
591
592 self.menu_tools = wx.Menu()
593 self.__gb['main.toolsmenu'] = self.menu_tools
594 self.mainmenu.Append(self.menu_tools, _("&Tools"))
595
596 ID_DICOM_VIEWER = wx.NewId()
597 viewer = _('no viewer installed')
598 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
599 viewer = u'OsiriX'
600 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
601 viewer = u'Aeskulap'
602 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
603 viewer = u'AMIDE'
604 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
605 viewer = u'(x)medcon'
606 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)
607 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
608 if viewer == _('no viewer installed'):
609 _log.info('neither of OsiriX / Aeskulap / AMIDE / xmedcon found, disabling "DICOM viewer" menu item')
610 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
611
612
613
614
615
616 ID = wx.NewId()
617 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
618 wx.EVT_MENU(self, ID, self.__on_snellen)
619
620 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
621 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
622
623 self.menu_tools.AppendSeparator()
624
625
626 menu_knowledge = wx.Menu()
627 self.__gb['main.knowledgemenu'] = menu_knowledge
628 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
629
630 menu_drug_dbs = wx.Menu()
631 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
632
633 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
634 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
635
636
637
638
639
640
641 menu_id = wx.NewId()
642 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
643 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
644
645
646
647
648 ID_MEDICAL_LINKS = wx.NewId()
649 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
650 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
651
652
653 self.menu_office = wx.Menu()
654
655 self.__gb['main.officemenu'] = self.menu_office
656 self.mainmenu.Append(self.menu_office, _('&Office'))
657
658
659 help_menu = wx.Menu()
660
661 ID = wx.NewId()
662 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
663 wx.EVT_MENU(self, ID, self.__on_display_wiki)
664
665 ID = wx.NewId()
666 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
667 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
668
669 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
670 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
671
672 menu_debugging = wx.Menu()
673 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
674
675 ID_SCREENSHOT = wx.NewId()
676 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
677 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
678
679 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
680 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
681
682 ID = wx.NewId()
683 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
684 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
685
686 ID = wx.NewId()
687 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
688 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
689
690 ID_UNBLOCK = wx.NewId()
691 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
692 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
693
694 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
695 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
696
697
698
699
700 if _cfg.get(option = 'debug'):
701 ID_TOGGLE_PAT_LOCK = wx.NewId()
702 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient'), _('Lock/unlock patient - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
703 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
704
705 ID_TEST_EXCEPTION = wx.NewId()
706 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
707 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
708
709 ID = wx.NewId()
710 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
711 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
712 try:
713 import wx.lib.inspection
714 except ImportError:
715 menu_debugging.Enable(id = ID, enable = False)
716
717 help_menu.AppendSeparator()
718
719 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
720 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
721
722 ID_CONTRIBUTORS = wx.NewId()
723 help_menu.Append(ID_CONTRIBUTORS, _('GNUmed contributors'), _('show GNUmed contributors'))
724 wx.EVT_MENU(self, ID_CONTRIBUTORS, self.__on_show_contributors)
725
726 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
727 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
728
729 help_menu.AppendSeparator()
730
731
732 self.__gb['main.helpmenu'] = help_menu
733 self.mainmenu.Append(help_menu, _("&Help"))
734
735
736
737 self.SetMenuBar(self.mainmenu)
738
741
742
743
745 """register events we want to react to"""
746
747 wx.EVT_CLOSE(self, self.OnClose)
748 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
749 wx.EVT_END_SESSION(self, self._on_end_session)
750
751 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
752 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
753 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
754 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
755 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
756 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
757 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
758 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
759
760 wx.lib.pubsub.Publisher().subscribe(listener = self._on_set_statustext_pubsub, topic = 'statustext')
761
762 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
763
764 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
765
766 _log.debug('registering plugin with menu system')
767 _log.debug(' generic name: %s', plugin_name)
768 _log.debug(' class name: %s', class_name)
769 _log.debug(' specific menu: %s', menu_name)
770 _log.debug(' menu item: %s', menu_item_name)
771
772
773 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
774 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
775 self.menu_id2plugin[item.Id] = class_name
776
777
778 if menu_name is not None:
779 menu = self.__gb['main.%smenu' % menu_name]
780 item = menu.Append(-1, menu_item_name, menu_help_string)
781 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
782 self.menu_id2plugin[item.Id] = class_name
783
784 return True
785
787 gmDispatcher.send (
788 signal = u'display_widget',
789 name = self.menu_id2plugin[evt.Id]
790 )
791
793 wx.Bell()
794 wx.Bell()
795 wx.Bell()
796 _log.warning('unhandled event detected: QUERY_END_SESSION')
797 _log.info('we should be saving ourselves from here')
798 gmLog2.flush()
799 print "unhandled event detected: QUERY_END_SESSION"
800
802 wx.Bell()
803 wx.Bell()
804 wx.Bell()
805 _log.warning('unhandled event detected: END_SESSION')
806 gmLog2.flush()
807 print "unhandled event detected: END_SESSION"
808
810 if not callable(callback):
811 raise TypeError(u'callback [%s] not callable' % callback)
812
813 self.__pre_exit_callbacks.append(callback)
814
815 - def _on_set_statustext_pubsub(self, context=None):
816 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
817 wx.CallAfter(self.SetStatusText, msg)
818
819 try:
820 if context.data['beep']:
821 wx.Bell()
822 except KeyError:
823 pass
824
825 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
826
827 if msg is None:
828 msg = _('programmer forgot to specify status message')
829
830 if loglevel is not None:
831 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
832
833 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
834 wx.CallAfter(self.SetStatusText, msg)
835
836 if beep:
837 wx.Bell()
838
840 wx.CallAfter(self.__on_db_maintenance_warning)
841
843
844 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
845 wx.Bell()
846 if not wx.GetApp().IsActive():
847 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
848
849 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
850
851 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
852 None,
853 -1,
854 caption = _('Database shutdown warning'),
855 question = _(
856 'The database will be shut down for maintenance\n'
857 'in a few minutes.\n'
858 '\n'
859 'In order to not suffer any loss of data you\n'
860 'will need to save your current work and log\n'
861 'out of this GNUmed client.\n'
862 ),
863 button_defs = [
864 {
865 u'label': _('Close now'),
866 u'tooltip': _('Close this GNUmed client immediately.'),
867 u'default': False
868 },
869 {
870 u'label': _('Finish work'),
871 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
872 u'default': True
873 }
874 ]
875 )
876 decision = dlg.ShowModal()
877 if decision == wx.ID_YES:
878 top_win = wx.GetApp().GetTopWindow()
879 wx.CallAfter(top_win.Close)
880
882 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
883
885
886 if not wx.GetApp().IsActive():
887 if urgent:
888 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
889 else:
890 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
891
892 if msg is not None:
893 self.SetStatusText(msg)
894
895 if urgent:
896 wx.Bell()
897
898 gmHooks.run_hook_script(hook = u'request_user_attention')
899
901 wx.CallAfter(self.__on_pat_name_changed)
902
904 self.__update_window_title()
905
907 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
908
910 self.__update_window_title()
911 try:
912 gmHooks.run_hook_script(hook = u'post_patient_activation')
913 except:
914 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
915 raise
916
918 return self.__sanity_check_encounter()
919
977
978
979
982
990
993
994
995
1010
1033
1035 from Gnumed.wxpython import gmAbout
1036 contribs = gmAbout.cContributorsDlg (
1037 parent = self,
1038 id = -1,
1039 title = _('GNUmed contributors'),
1040 size = wx.Size(400,600),
1041 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1042 )
1043 contribs.ShowModal()
1044 del contribs
1045 del gmAbout
1046
1047
1048
1050 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1051 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1052 self.Close(True)
1053 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1054
1057
1059 send = gmGuiHelpers.gm_show_question (
1060 _('This will send a notification about database downtime\n'
1061 'to all GNUmed clients connected to your database.\n'
1062 '\n'
1063 'Do you want to send the notification ?\n'
1064 ),
1065 _('Announcing database maintenance downtime')
1066 )
1067 if not send:
1068 return
1069 gmPG2.send_maintenance_notification()
1070
1071
1074
1075
1076
1108
1121
1122 gmCfgWidgets.configure_string_option (
1123 message = _(
1124 'Some network installations cannot cope with loading\n'
1125 'documents of arbitrary size in one piece from the\n'
1126 'database (mainly observed on older Windows versions)\n.'
1127 '\n'
1128 'Under such circumstances documents need to be retrieved\n'
1129 'in chunks and reassembled on the client.\n'
1130 '\n'
1131 'Here you can set the size (in Bytes) above which\n'
1132 'GNUmed will retrieve documents in chunks. Setting this\n'
1133 'value to 0 will disable the chunking protocol.'
1134 ),
1135 option = 'horstspace.blob_export_chunk_size',
1136 bias = 'workplace',
1137 default_value = 1024 * 1024,
1138 validator = is_valid
1139 )
1140
1141
1142
1210
1214
1215
1216
1225
1226 gmCfgWidgets.configure_string_option (
1227 message = _(
1228 'When GNUmed cannot find an OpenOffice server it\n'
1229 'will try to start one. OpenOffice, however, needs\n'
1230 'some time to fully start up.\n'
1231 '\n'
1232 'Here you can set the time for GNUmed to wait for OOo.\n'
1233 ),
1234 option = 'external.ooo.startup_settle_time',
1235 bias = 'workplace',
1236 default_value = 2.0,
1237 validator = is_valid
1238 )
1239
1242
1254
1255 gmCfgWidgets.configure_string_option (
1256 message = _(
1257 'GNUmed will use this URL to access an encyclopedia of\n'
1258 'measurement/lab methods from within the measurments grid.\n'
1259 '\n'
1260 'You can leave this empty but to set it to a specific\n'
1261 'address the URL must be accessible now.'
1262 ),
1263 option = 'external.urls.measurements_encyclopedia',
1264 bias = 'user',
1265 default_value = u'http://www.laborlexikon.de',
1266 validator = is_valid
1267 )
1268
1281
1282 gmCfgWidgets.configure_string_option (
1283 message = _(
1284 'Enter the shell command with which to start the\n'
1285 'the ACS risk assessment calculator.\n'
1286 '\n'
1287 'GNUmed will try to verify the path which may,\n'
1288 'however, fail if you are using an emulator such\n'
1289 'as Wine. Nevertheless, starting the calculator\n'
1290 'will work as long as the shell command is correct\n'
1291 'despite the failing test.'
1292 ),
1293 option = 'external.tools.acs_risk_calculator_cmd',
1294 bias = 'user',
1295 validator = is_valid
1296 )
1297
1300
1313
1314 gmCfgWidgets.configure_string_option (
1315 message = _(
1316 'Enter the shell command with which to start\n'
1317 'the FreeDiams drug database frontend.\n'
1318 '\n'
1319 'GNUmed will try to verify that path.'
1320 ),
1321 option = 'external.tools.freediams_cmd',
1322 bias = 'workplace',
1323 default_value = None,
1324 validator = is_valid
1325 )
1326
1339
1340 gmCfgWidgets.configure_string_option (
1341 message = _(
1342 'Enter the shell command with which to start the\n'
1343 'the IFAP drug database.\n'
1344 '\n'
1345 'GNUmed will try to verify the path which may,\n'
1346 'however, fail if you are using an emulator such\n'
1347 'as Wine. Nevertheless, starting IFAP will work\n'
1348 'as long as the shell command is correct despite\n'
1349 'the failing test.'
1350 ),
1351 option = 'external.ifap-win.shell_command',
1352 bias = 'workplace',
1353 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1354 validator = is_valid
1355 )
1356
1357
1358
1407
1408
1409
1426
1429
1432
1437
1438 gmCfgWidgets.configure_string_option (
1439 message = _(
1440 'When a patient is activated GNUmed checks the\n'
1441 "proximity of the patient's birthday.\n"
1442 '\n'
1443 'If the birthday falls within the range of\n'
1444 ' "today %s <the interval you set here>"\n'
1445 'GNUmed will remind you of the recent or\n'
1446 'imminent anniversary.'
1447 ) % u'\u2213',
1448 option = u'patient_search.dob_warn_interval',
1449 bias = 'user',
1450 default_value = '1 week',
1451 validator = is_valid
1452 )
1453
1455
1456 gmCfgWidgets.configure_boolean_option (
1457 parent = self,
1458 question = _(
1459 'When adding progress notes do you want to\n'
1460 'allow opening several unassociated, new\n'
1461 'episodes for a patient at once ?\n'
1462 '\n'
1463 'This can be particularly helpful when entering\n'
1464 'progress notes on entirely new patients presenting\n'
1465 'with a multitude of problems on their first visit.'
1466 ),
1467 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1468 button_tooltips = [
1469 _('Yes, allow for multiple new episodes concurrently.'),
1470 _('No, only allow editing one new episode at a time.')
1471 ]
1472 )
1473
1519
1520
1521
1524
1538
1540 gmCfgWidgets.configure_boolean_option (
1541 parent = self,
1542 question = _(
1543 'Do you want GNUmed to show the encounter\n'
1544 'details editor when changing the active patient ?'
1545 ),
1546 option = 'encounter.show_editor_before_patient_change',
1547 button_tooltips = [
1548 _('Yes, show the encounter editor if it seems appropriate.'),
1549 _('No, never show the encounter editor even if it would seem useful.')
1550 ]
1551 )
1552
1557
1558 gmCfgWidgets.configure_string_option (
1559 message = _(
1560 'When a patient is activated GNUmed checks the\n'
1561 'chart for encounters lacking any entries.\n'
1562 '\n'
1563 'Any such encounters older than what you set\n'
1564 'here will be removed from the medical record.\n'
1565 '\n'
1566 'To effectively disable removal of such encounters\n'
1567 'set this option to an improbable value.\n'
1568 ),
1569 option = 'encounter.ttl_if_empty',
1570 bias = 'user',
1571 default_value = '1 week',
1572 validator = is_valid
1573 )
1574
1579
1580 gmCfgWidgets.configure_string_option (
1581 message = _(
1582 'When a patient is activated GNUmed checks the\n'
1583 'age of the most recent encounter.\n'
1584 '\n'
1585 'If that encounter is younger than this age\n'
1586 'the existing encounter will be continued.\n'
1587 '\n'
1588 '(If it is really old a new encounter is\n'
1589 ' started, or else GNUmed will ask you.)\n'
1590 ),
1591 option = 'encounter.minimum_ttl',
1592 bias = 'user',
1593 default_value = '1 hour 30 minutes',
1594 validator = is_valid
1595 )
1596
1601
1602 gmCfgWidgets.configure_string_option (
1603 message = _(
1604 'When a patient is activated GNUmed checks the\n'
1605 'age of the most recent encounter.\n'
1606 '\n'
1607 'If that encounter is older than this age\n'
1608 'GNUmed will always start a new encounter.\n'
1609 '\n'
1610 '(If it is very recent the existing encounter\n'
1611 ' is continued, or else GNUmed will ask you.)\n'
1612 ),
1613 option = 'encounter.maximum_ttl',
1614 bias = 'user',
1615 default_value = '6 hours',
1616 validator = is_valid
1617 )
1618
1627
1628 gmCfgWidgets.configure_string_option (
1629 message = _(
1630 'At any time there can only be one open (ongoing)\n'
1631 'episode for each health issue.\n'
1632 '\n'
1633 'When you try to open (add data to) an episode on a health\n'
1634 'issue GNUmed will check for an existing open episode on\n'
1635 'that issue. If there is any it will check the age of that\n'
1636 'episode. The episode is closed if it has been dormant (no\n'
1637 'data added, that is) for the period of time (in days) you\n'
1638 'set here.\n'
1639 '\n'
1640 "If the existing episode hasn't been dormant long enough\n"
1641 'GNUmed will consult you what to do.\n'
1642 '\n'
1643 'Enter maximum episode dormancy in DAYS:'
1644 ),
1645 option = 'episode.ttl',
1646 bias = 'user',
1647 default_value = 60,
1648 validator = is_valid
1649 )
1650
1681
1684
1699
1724
1736
1737 gmCfgWidgets.configure_string_option (
1738 message = _(
1739 'GNUmed can check for new releases being available. To do\n'
1740 'so it needs to load version information from an URL.\n'
1741 '\n'
1742 'The default URL is:\n'
1743 '\n'
1744 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1745 '\n'
1746 'but you can configure any other URL locally. Note\n'
1747 'that you must enter the location as a valid URL.\n'
1748 'Depending on the URL the client will need online\n'
1749 'access when checking for updates.'
1750 ),
1751 option = u'horstspace.update.url',
1752 bias = u'workplace',
1753 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1754 validator = is_valid
1755 )
1756
1774
1791
1802
1803 gmCfgWidgets.configure_string_option (
1804 message = _(
1805 'GNUmed can show the document review dialog after\n'
1806 'calling the appropriate viewer for that document.\n'
1807 '\n'
1808 'Select the conditions under which you want\n'
1809 'GNUmed to do so:\n'
1810 '\n'
1811 ' 0: never display the review dialog\n'
1812 ' 1: always display the dialog\n'
1813 ' 2: only if there is no previous review by me\n'
1814 '\n'
1815 'Note that if a viewer is configured to not block\n'
1816 'GNUmed during document display the review dialog\n'
1817 'will actually appear in parallel to the viewer.'
1818 ),
1819 option = u'horstspace.document_viewer.review_after_display',
1820 bias = u'user',
1821 default_value = 2,
1822 validator = is_valid
1823 )
1824
1838
1840
1841 dbcfg = gmCfg.cCfgSQL()
1842 cmd = dbcfg.get2 (
1843 option = u'external.tools.acs_risk_calculator_cmd',
1844 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1845 bias = 'user'
1846 )
1847
1848 if cmd is None:
1849 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
1850 return
1851
1852 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
1853 try:
1854 subprocess.check_call (
1855 args = (cmd,),
1856 close_fds = True,
1857 cwd = cwd
1858 )
1859 except (OSError, ValueError, subprocess.CalledProcessError):
1860 _log.exception('there was a problem executing [%s]', cmd)
1861 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
1862 return
1863
1864 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
1865 for pdf in pdfs:
1866 try:
1867 open(pdf).close()
1868 except:
1869 _log.exception('error accessing [%s]', pdf)
1870 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the ARRIBA result in [%s] !') % pdf, beep = True)
1871 continue
1872
1873 doc = gmDocumentWidgets.save_file_as_new_document (
1874 parent = self,
1875 filename = pdf,
1876 document_type = u'risk assessment'
1877 )
1878
1879 try:
1880 os.remove(pdf)
1881 except StandardError:
1882 _log.exception('cannot remove [%s]', pdf)
1883
1884 if doc is None:
1885 continue
1886 doc['comment'] = u'ARRIBA: %s' % _('cardiovascular risk assessment')
1887 doc.save()
1888
1889 return
1890
1892 dlg = gmSnellen.cSnellenCfgDlg()
1893 if dlg.ShowModal() != wx.ID_OK:
1894 return
1895
1896 frame = gmSnellen.cSnellenChart (
1897 width = dlg.vals[0],
1898 height = dlg.vals[1],
1899 alpha = dlg.vals[2],
1900 mirr = dlg.vals[3],
1901 parent = None
1902 )
1903 frame.CentreOnScreen(wx.BOTH)
1904
1905
1906 frame.Show(True)
1907
1908
1910 webbrowser.open (
1911 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MedicalContentLinks#AnchorLocaleI%s' % gmI18N.system_locale_level['language'],
1912 new = False,
1913 autoraise = True
1914 )
1915
1918
1920 webbrowser.open (
1921 url = 'http://www.kompendium.ch',
1922 new = False,
1923 autoraise = True
1924 )
1925
1926
1927
1929 wx.CallAfter(self.__save_screenshot)
1930 evt.Skip()
1931
1933
1934 time.sleep(0.5)
1935
1936 rect = self.GetRect()
1937
1938
1939 if sys.platform == 'linux2':
1940 client_x, client_y = self.ClientToScreen((0, 0))
1941 border_width = client_x - rect.x
1942 title_bar_height = client_y - rect.y
1943
1944 if self.GetMenuBar():
1945 title_bar_height /= 2
1946 rect.width += (border_width * 2)
1947 rect.height += title_bar_height + border_width
1948
1949 wdc = wx.ScreenDC()
1950 mdc = wx.MemoryDC()
1951 img = wx.EmptyBitmap(rect.width, rect.height)
1952 mdc.SelectObject(img)
1953 mdc.Blit (
1954 0, 0,
1955 rect.width, rect.height,
1956 wdc,
1957 rect.x, rect.y
1958 )
1959
1960
1961 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
1962 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
1963 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
1964
1966
1967 raise ValueError('raised ValueError to test exception handling')
1968
1970 import wx.lib.inspection
1971 wx.lib.inspection.InspectionTool().Show()
1972
1974 webbrowser.open (
1975 url = 'https://bugs.launchpad.net/gnumed/',
1976 new = False,
1977 autoraise = True
1978 )
1979
1981 webbrowser.open (
1982 url = 'http://wiki.gnumed.de',
1983 new = False,
1984 autoraise = True
1985 )
1986
1988 webbrowser.open (
1989 url = 'http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual#UserGuideInManual',
1990 new = False,
1991 autoraise = True
1992 )
1993
1995 webbrowser.open (
1996 url = 'http://wiki.gnumed.de/bin/view/Gnumed/MenuReference',
1997 new = False,
1998 autoraise = True
1999 )
2000
2007
2011
2014
2021
2026
2028 name = os.path.basename(gmLog2._logfile_name)
2029 name, ext = os.path.splitext(name)
2030 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2031 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2032
2033 dlg = wx.FileDialog (
2034 parent = self,
2035 message = _("Save current log as..."),
2036 defaultDir = new_path,
2037 defaultFile = new_name,
2038 wildcard = "%s (*.log)|*.log" % _("log files"),
2039 style = wx.SAVE
2040 )
2041 choice = dlg.ShowModal()
2042 new_name = dlg.GetPath()
2043 dlg.Destroy()
2044 if choice != wx.ID_OK:
2045 return True
2046
2047 _log.warning('syncing log file for backup to [%s]', new_name)
2048 gmLog2.flush()
2049 shutil.copy2(gmLog2._logfile_name, new_name)
2050 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2051
2052
2053
2055 """This is the wx.EVT_CLOSE handler.
2056
2057 - framework still functional
2058 """
2059 _log.debug('gmTopLevelFrame.OnClose() start')
2060 self._clean_exit()
2061 self.Destroy()
2062 _log.debug('gmTopLevelFrame.OnClose() end')
2063 return True
2064
2070
2075
2083
2090
2097
2107
2115
2123
2131
2139
2147
2149 pat = gmPerson.gmCurrentPatient()
2150 if not pat.connected:
2151 gmDispatcher.send(signal = 'statustext', msg = _('Cannot show EMR summary. No active patient.'))
2152 return False
2153
2154 emr = pat.get_emr()
2155 dlg = wx.MessageDialog (
2156 parent = self,
2157 message = emr.format_statistics(),
2158 caption = _('EMR Summary'),
2159 style = wx.OK | wx.STAY_ON_TOP
2160 )
2161 dlg.ShowModal()
2162 dlg.Destroy()
2163 return True
2164
2167
2170
2172
2173 pat = gmPerson.gmCurrentPatient()
2174 if not pat.connected:
2175 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2176 return False
2177
2178 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2179
2180 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2181 gmTools.mkdir(aDefDir)
2182
2183 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2184 dlg = wx.FileDialog (
2185 parent = self,
2186 message = _("Save patient's EMR journal as..."),
2187 defaultDir = aDefDir,
2188 defaultFile = fname,
2189 wildcard = aWildcard,
2190 style = wx.SAVE
2191 )
2192 choice = dlg.ShowModal()
2193 fname = dlg.GetPath()
2194 dlg.Destroy()
2195 if choice != wx.ID_OK:
2196 return True
2197
2198 _log.debug('exporting EMR journal to [%s]' % fname)
2199
2200 exporter = gmPatientExporter.cEMRJournalExporter()
2201
2202 wx.BeginBusyCursor()
2203 try:
2204 fname = exporter.export_to_file(filename = fname)
2205 except:
2206 wx.EndBusyCursor()
2207 gmGuiHelpers.gm_show_error (
2208 _('Error exporting patient EMR as chronological journal.'),
2209 _('EMR journal export')
2210 )
2211 raise
2212 wx.EndBusyCursor()
2213
2214 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2215
2216 return True
2217
2224
2234
2236 curr_pat = gmPerson.gmCurrentPatient()
2237 if not curr_pat.connected:
2238 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2239 return False
2240
2241 enc = 'cp850'
2242 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2243 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2244 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2245
2248
2249
2250
2251
2252
2253
2254
2262
2270
2273
2282
2286
2290
2293
2296
2299
2302
2305
2308
2311
2314
2317
2320
2323
2326
2328 """Cleanup helper.
2329
2330 - should ALWAYS be called when this program is
2331 to be terminated
2332 - ANY code that should be executed before a
2333 regular shutdown should go in here
2334 - framework still functional
2335 """
2336 _log.debug('gmTopLevelFrame._clean_exit() start')
2337
2338
2339 listener = gmBackendListener.gmBackendListener()
2340 try:
2341 listener.shutdown()
2342 except:
2343 _log.exception('cannot stop backend notifications listener thread')
2344
2345
2346 if _scripting_listener is not None:
2347 try:
2348 _scripting_listener.shutdown()
2349 except:
2350 _log.exception('cannot stop scripting listener thread')
2351
2352
2353 self.clock_update_timer.Stop()
2354 gmTimer.shutdown()
2355 gmPhraseWheel.shutdown()
2356
2357
2358 for call_back in self.__pre_exit_callbacks:
2359 try:
2360 call_back()
2361 except:
2362 print "*** pre-exit callback failed ***"
2363 print call_back
2364 _log.exception('callback [%s] failed', call_back)
2365
2366
2367 gmDispatcher.send(u'application_closing')
2368
2369
2370 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2371
2372
2373 curr_width, curr_height = self.GetClientSizeTuple()
2374 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2375 dbcfg = gmCfg.cCfgSQL()
2376 dbcfg.set (
2377 option = 'main.window.width',
2378 value = curr_width,
2379 workplace = gmSurgery.gmCurrentPractice().active_workplace
2380 )
2381 dbcfg.set (
2382 option = 'main.window.height',
2383 value = curr_height,
2384 workplace = gmSurgery.gmCurrentPractice().active_workplace
2385 )
2386
2387 if _cfg.get(option = 'debug'):
2388 print '---=== GNUmed shutdown ===---'
2389 print _('You have to manually close this window to finalize shutting down GNUmed.')
2390 print _('This is so that you can inspect the console output at your leisure.')
2391 print '---=== GNUmed shutdown ===---'
2392
2393
2394 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2395
2396
2397 import threading
2398 _log.debug("%s active threads", threading.activeCount())
2399 for t in threading.enumerate():
2400 _log.debug('thread %s', t)
2401
2402 _log.debug('gmTopLevelFrame._clean_exit() end')
2403
2404
2405
2407
2408 if _cfg.get(option = 'slave'):
2409 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2410 _cfg.get(option = 'slave personality'),
2411 _cfg.get(option = 'xml-rpc port')
2412 )
2413 else:
2414 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2415
2417 """Update title of main window based on template.
2418
2419 This gives nice tooltips on iconified GNUmed instances.
2420
2421 User research indicates that in the title bar people want
2422 the date of birth, not the age, so please stick to this
2423 convention.
2424 """
2425 args = {}
2426
2427 pat = gmPerson.gmCurrentPatient()
2428 if pat.connected:
2429
2430
2431
2432
2433
2434
2435 args['pat'] = u'%s %s %s (%s) #%d' % (
2436 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2437
2438 pat['firstnames'],
2439 pat['lastnames'],
2440 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2441 pat['pk_identity']
2442 )
2443 else:
2444 args['pat'] = _('no patient')
2445
2446 args['prov'] = u'%s%s.%s' % (
2447 gmTools.coalesce(_provider['title'], u'', u'%s '),
2448 _provider['firstnames'][:1],
2449 _provider['lastnames']
2450 )
2451
2452 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2453
2454 self.SetTitle(self.__title_template % args)
2455
2456
2458 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2459 sb.SetStatusWidths([-1, 225])
2460
2461 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2462 self._cb_update_clock()
2463
2464 self.clock_update_timer.Start(milliseconds = 1000)
2465
2467 """Displays date and local time in the second slot of the status bar"""
2468 t = time.localtime(time.time())
2469 st = time.strftime('%c', t).decode(gmI18N.get_encoding())
2470 self.SetStatusText(st,1)
2471
2473 """Lock GNUmed client against unauthorized access"""
2474
2475
2476
2477 return
2478
2480 """Unlock the main notebook widgets
2481 As long as we are not logged into the database backend,
2482 all pages but the 'login' page of the main notebook widget
2483 are locked; i.e. not accessible by the user
2484 """
2485
2486
2487
2488
2489
2490 return
2491
2493 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2494
2496
2498
2499 self.__starting_up = True
2500
2501 gmExceptionHandlingWidgets.install_wx_exception_handler()
2502 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2503
2504
2505
2506
2507 self.SetAppName(u'gnumed')
2508 self.SetVendorName(u'The GNUmed Development Community.')
2509 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2510 paths.init_paths(wx = wx, app_name = u'gnumed')
2511
2512 if not self.__setup_prefs_file():
2513 return False
2514
2515 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2516
2517 self.__guibroker = gmGuiBroker.GuiBroker()
2518 self.__setup_platform()
2519
2520 if not self.__establish_backend_connection():
2521 return False
2522
2523 if not _cfg.get(option = 'skip-update-check'):
2524 self.__check_for_updates()
2525
2526 if _cfg.get(option = 'slave'):
2527 if not self.__setup_scripting_listener():
2528 return False
2529
2530
2531 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640,440))
2532 frame.CentreOnScreen(wx.BOTH)
2533 self.SetTopWindow(frame)
2534 frame.Show(True)
2535
2536 if _cfg.get(option = 'debug'):
2537 self.RedirectStdio()
2538 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2539
2540
2541 print '---=== GNUmed startup ===---'
2542 print _('redirecting STDOUT/STDERR to this log window')
2543 print '---=== GNUmed startup ===---'
2544
2545 self.__setup_user_activity_timer()
2546 self.__register_events()
2547
2548 wx.CallAfter(self._do_after_init)
2549
2550 return True
2551
2553 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2554
2555 - after destroying all application windows and controls
2556 - before wx.Windows internal cleanup
2557 """
2558 _log.debug('gmApp.OnExit() start')
2559
2560 self.__shutdown_user_activity_timer()
2561
2562 if _cfg.get(option = 'debug'):
2563 self.RestoreStdio()
2564 sys.stdin = sys.__stdin__
2565 sys.stdout = sys.__stdout__
2566 sys.stderr = sys.__stderr__
2567
2568 _log.debug('gmApp.OnExit() end')
2569
2571 wx.Bell()
2572 wx.Bell()
2573 wx.Bell()
2574 _log.warning('unhandled event detected: QUERY_END_SESSION')
2575 _log.info('we should be saving ourselves from here')
2576 gmLog2.flush()
2577 print "unhandled event detected: QUERY_END_SESSION"
2578
2580 wx.Bell()
2581 wx.Bell()
2582 wx.Bell()
2583 _log.warning('unhandled event detected: END_SESSION')
2584 gmLog2.flush()
2585 print "unhandled event detected: END_SESSION"
2586
2597
2599 self.user_activity_detected = True
2600 evt.Skip()
2601
2603
2604 if self.user_activity_detected:
2605 self.elapsed_inactivity_slices = 0
2606 self.user_activity_detected = False
2607 self.elapsed_inactivity_slices += 1
2608 else:
2609 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2610
2611 pass
2612
2613 self.user_activity_timer.Start(oneShot = True)
2614
2615
2616
2618 try:
2619 kwargs['originated_in_database']
2620 print '==> got notification from database "%s":' % kwargs['signal']
2621 except KeyError:
2622 print '==> received signal from client: "%s"' % kwargs['signal']
2623
2624 del kwargs['signal']
2625 for key in kwargs.keys():
2626 print ' [%s]: %s' % (key, kwargs[key])
2627
2629 print "wx.lib.pubsub message:"
2630 print msg.topic
2631 print msg.data
2632
2638
2640 self.user_activity_detected = True
2641 self.elapsed_inactivity_slices = 0
2642
2643 self.max_user_inactivity_slices = 15
2644 self.user_activity_timer = gmTimer.cTimer (
2645 callback = self._on_user_activity_timer_expired,
2646 delay = 2000
2647 )
2648 self.user_activity_timer.Start(oneShot=True)
2649
2651 try:
2652 self.user_activity_timer.Stop()
2653 del self.user_activity_timer
2654 except:
2655 pass
2656
2658 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2659 wx.EVT_END_SESSION(self, self._on_end_session)
2660
2661
2662
2663
2664
2665 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2666
2667 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2668 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2694
2696 """Handle all the database related tasks necessary for startup."""
2697
2698
2699 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
2700
2701 from Gnumed.wxpython import gmAuthWidgets
2702 connected = gmAuthWidgets.connect_to_database (
2703 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
2704 require_version = not override
2705 )
2706 if not connected:
2707 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
2708 return False
2709
2710
2711 try:
2712 global _provider
2713 _provider = gmPerson.gmCurrentProvider(provider = gmPerson.cStaff())
2714 except ValueError:
2715 account = gmPG2.get_current_user()
2716 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
2717 msg = _(
2718 'The database account [%s] cannot be used as a\n'
2719 'staff member login for GNUmed. There was an\n'
2720 'error retrieving staff details for it.\n\n'
2721 'Please ask your administrator for help.\n'
2722 ) % account
2723 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
2724 return False
2725
2726
2727 tmp = '%s%s %s (%s = %s)' % (
2728 gmTools.coalesce(_provider['title'], ''),
2729 _provider['firstnames'],
2730 _provider['lastnames'],
2731 _provider['short_alias'],
2732 _provider['db_user']
2733 )
2734 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
2735
2736
2737 surgery = gmSurgery.gmCurrentPractice()
2738 msg = surgery.db_logon_banner
2739 if msg.strip() != u'':
2740
2741 login = gmPG2.get_default_login()
2742 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
2743 login.database,
2744 gmTools.coalesce(login.host, u'localhost')
2745 ))
2746 msg = auth + msg + u'\n\n'
2747
2748 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
2749 None,
2750 -1,
2751 caption = _('Verifying database'),
2752 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
2753 button_defs = [
2754 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
2755 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
2756 ]
2757 )
2758 go_on = dlg.ShowModal()
2759 dlg.Destroy()
2760 if go_on != wx.ID_YES:
2761 _log.info('user decided to not connect to this database')
2762 return False
2763
2764
2765 self.__check_db_lang()
2766
2767 return True
2768
2770 """Setup access to a config file for storing preferences."""
2771
2772 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2773
2774 candidates = []
2775 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
2776 if explicit_file is not None:
2777 candidates.append(explicit_file)
2778
2779 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
2780 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
2781 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
2782
2783 prefs_file = None
2784 for candidate in candidates:
2785 try:
2786 open(candidate, 'a+').close()
2787 prefs_file = candidate
2788 break
2789 except IOError:
2790 continue
2791
2792 if prefs_file is None:
2793 msg = _(
2794 'Cannot find configuration file in any of:\n'
2795 '\n'
2796 ' %s\n'
2797 'You may need to use the comand line option\n'
2798 '\n'
2799 ' --conf-file=<FILE>'
2800 ) % '\n '.join(candidates)
2801 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
2802 return False
2803
2804 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
2805 _log.info('user preferences file: %s', prefs_file)
2806
2807 return True
2808
2810
2811 from socket import error as SocketError
2812 from Gnumed.pycommon import gmScriptingListener
2813 from Gnumed.wxpython import gmMacro
2814
2815 slave_personality = gmTools.coalesce (
2816 _cfg.get (
2817 group = u'workplace',
2818 option = u'slave personality',
2819 source_order = [
2820 ('explicit', 'return'),
2821 ('workbase', 'return'),
2822 ('user', 'return'),
2823 ('system', 'return')
2824 ]
2825 ),
2826 u'gnumed-client'
2827 )
2828 _cfg.set_option(option = 'slave personality', value = slave_personality)
2829
2830
2831 port = int (
2832 gmTools.coalesce (
2833 _cfg.get (
2834 group = u'workplace',
2835 option = u'xml-rpc port',
2836 source_order = [
2837 ('explicit', 'return'),
2838 ('workbase', 'return'),
2839 ('user', 'return'),
2840 ('system', 'return')
2841 ]
2842 ),
2843 9999
2844 )
2845 )
2846 _cfg.set_option(option = 'xml-rpc port', value = port)
2847
2848 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
2849 global _scripting_listener
2850 try:
2851 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
2852 except SocketError, e:
2853 _log.exception('cannot start GNUmed XML-RPC server')
2854 gmGuiHelpers.gm_show_error (
2855 aMessage = (
2856 'Cannot start the GNUmed server:\n'
2857 '\n'
2858 ' [%s]'
2859 ) % e,
2860 aTitle = _('GNUmed startup')
2861 )
2862 return False
2863
2864 return True
2865
2885
2887 if gmI18N.system_locale is None or gmI18N.system_locale == '':
2888 _log.warning("system locale is undefined (probably meaning 'C')")
2889 return True
2890
2891
2892 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
2893 db_lang = rows[0]['lang']
2894
2895 if db_lang is None:
2896 _log.debug("database locale currently not set")
2897 msg = _(
2898 "There is no language selected in the database for user [%s].\n"
2899 "Your system language is currently set to [%s].\n\n"
2900 "Do you want to set the database language to '%s' ?\n\n"
2901 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
2902 checkbox_msg = _('Remember to ignore missing language')
2903 else:
2904 _log.debug("current database locale: [%s]" % db_lang)
2905 msg = _(
2906 "The currently selected database language ('%s') does\n"
2907 "not match the current system language ('%s').\n"
2908 "\n"
2909 "Do you want to set the database language to '%s' ?\n"
2910 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
2911 checkbox_msg = _('Remember to ignore language mismatch')
2912
2913
2914 if db_lang == gmI18N.system_locale_level['full']:
2915 _log.debug('Database locale (%s) up to date.' % db_lang)
2916 return True
2917 if db_lang == gmI18N.system_locale_level['country']:
2918 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
2919 return True
2920 if db_lang == gmI18N.system_locale_level['language']:
2921 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
2922 return True
2923
2924 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
2925
2926
2927 ignored_sys_lang = _cfg.get (
2928 group = u'backend',
2929 option = u'ignored mismatching system locale',
2930 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
2931 )
2932
2933
2934 if gmI18N.system_locale == ignored_sys_lang:
2935 _log.info('configured to ignore system-to-database locale mismatch')
2936 return True
2937
2938
2939 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
2940 None,
2941 -1,
2942 caption = _('Checking database language settings'),
2943 question = msg,
2944 button_defs = [
2945 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
2946 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
2947 ],
2948 show_checkbox = True,
2949 checkbox_msg = checkbox_msg,
2950 checkbox_tooltip = _(
2951 'Checking this will make GNUmed remember your decision\n'
2952 'until the system language is changed.\n'
2953 '\n'
2954 'You can also reactivate this inquiry by removing the\n'
2955 'corresponding "ignore" option from the configuration file\n'
2956 '\n'
2957 ' [%s]'
2958 ) % _cfg.get(option = 'user_preferences_file')
2959 )
2960 decision = dlg.ShowModal()
2961 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
2962 dlg.Destroy()
2963
2964 if decision == wx.ID_NO:
2965 if not remember_ignoring_problem:
2966 return True
2967 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
2968 gmCfg2.set_option_in_INI_file (
2969 filename = _cfg.get(option = 'user_preferences_file'),
2970 group = 'backend',
2971 option = 'ignored mismatching system locale',
2972 value = gmI18N.system_locale
2973 )
2974 return True
2975
2976
2977 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
2978 if len(lang) > 0:
2979
2980
2981 rows, idx = gmPG2.run_rw_queries (
2982 link_obj = None,
2983 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
2984 return_data = True
2985 )
2986 if rows[0][0]:
2987 _log.debug("Successfully set database language to [%s]." % lang)
2988 else:
2989 _log.error('Cannot set database language to [%s].' % lang)
2990 continue
2991 return True
2992
2993
2994 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
2995 gmPG2.run_rw_queries(queries = [{
2996 'cmd': u'select i18n.force_curr_lang(%s)',
2997 'args': [gmI18N.system_locale_level['country']]
2998 }])
2999
3000 return True
3001
3003 try:
3004 kwargs['originated_in_database']
3005 print '==> got notification from database "%s":' % kwargs['signal']
3006 except KeyError:
3007 print '==> received signal from client: "%s"' % kwargs['signal']
3008
3009 del kwargs['signal']
3010 for key in kwargs.keys():
3011
3012 try: print ' [%s]: %s' % (key, kwargs[key])
3013 except: print 'cannot print signal information'
3014
3016
3017 try:
3018 print '==> received wx.lib.pubsub message: "%s"' % msg.topic
3019 print ' data: %s' % msg.data
3020 print msg
3021 except: print 'problem printing pubsub message information'
3022
3024
3025 if _cfg.get(option = 'debug'):
3026 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3027 _log.debug('gmDispatcher signal monitor activated')
3028 wx.lib.pubsub.Publisher().subscribe (
3029 listener = _signal_debugging_monitor_pubsub,
3030 topic = wx.lib.pubsub.getStrAllTopics()
3031 )
3032 _log.debug('wx.lib.pubsub signal monitor activated')
3033
3034
3035
3036
3037 app = gmApp(redirect = False, clearSigInt = False)
3038 app.MainLoop()
3039
3040
3041
3042 if __name__ == '__main__':
3043
3044 from GNUmed.pycommon import gmI18N
3045 gmI18N.activate_locale()
3046 gmI18N.install_domain()
3047
3048 _log.info('Starting up as main module.')
3049 main()
3050
3051
3052