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