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