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