1
2
3 __doc__ = """GNUmed GUI client.
4
5 This contains the GUI application framework and main window
6 of the all signing all dancing GNUmed Python Reference
7 client. It relies on the <gnumed.py> launcher having set up
8 the non-GUI-related runtime environment.
9
10 copyright: authors
11 """
12
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
20 import time
21 import os
22 import os.path
23 import datetime as pyDT
24 import shutil
25 import logging
26 import urllib.request
27 import subprocess
28 import glob
29 import io
30
31 _log = logging.getLogger('gm.main')
32
33
34
35 from Gnumed.pycommon import gmCfg2
36 _cfg = gmCfg2.gmCfgData()
37
38
39
40 try:
41 import wx
42 _log.info('wxPython version loaded: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
43 except ImportError:
44 _log.exception('cannot import wxPython')
45 print('GNUmed startup: Cannot import wxPython library.')
46 print('GNUmed startup: Make sure wxPython is installed.')
47 print('CRITICAL ERROR: Error importing wxPython. Halted.')
48 raise
49
50
51
52 version = int('%s%s' % (wx.MAJOR_VERSION, wx.MINOR_VERSION))
53 if (version < 28) or ('unicode' not in wx.PlatformInfo):
54 print('GNUmed startup: Unsupported wxPython version (%s: %s).' % (wx.VERSION_STRING, wx.PlatformInfo))
55 print('GNUmed startup: wxPython 2.8+ with unicode support is required.')
56 print('CRITICAL ERROR: Proper wxPython version not found. Halted.')
57 raise ValueError('wxPython 2.8+ with unicode support not found')
58
59
60
61 from Gnumed.pycommon import gmCfg
62 from Gnumed.pycommon import gmPG2
63 from Gnumed.pycommon import gmDispatcher
64 from Gnumed.pycommon import gmGuiBroker
65 from Gnumed.pycommon import gmI18N
66 from Gnumed.pycommon import gmExceptions
67 from Gnumed.pycommon import gmShellAPI
68 from Gnumed.pycommon import gmTools
69 from Gnumed.pycommon import gmDateTime
70 from Gnumed.pycommon import gmHooks
71 from Gnumed.pycommon import gmBackendListener
72 from Gnumed.pycommon import gmLog2
73 from Gnumed.pycommon import gmNetworkTools
74 from Gnumed.pycommon import gmMimeLib
75
76 from Gnumed.business import gmPerson
77 from Gnumed.business import gmClinicalRecord
78 from Gnumed.business import gmPraxis
79 from Gnumed.business import gmEMRStructItems
80 from Gnumed.business import gmArriba
81 from Gnumed.business import gmStaff
82
83 from Gnumed.exporters import gmPatientExporter
84
85 from Gnumed.wxpython import gmGuiHelpers
86 from Gnumed.wxpython import gmHorstSpace
87 from Gnumed.wxpython import gmDemographicsWidgets
88 from Gnumed.wxpython import gmPersonCreationWidgets
89 from Gnumed.wxpython import gmEMRStructWidgets
90 from Gnumed.wxpython import gmPatSearchWidgets
91 from Gnumed.wxpython import gmAllergyWidgets
92 from Gnumed.wxpython import gmListWidgets
93 from Gnumed.wxpython import gmProviderInboxWidgets
94 from Gnumed.wxpython import gmCfgWidgets
95 from Gnumed.wxpython import gmExceptionHandlingWidgets
96 from Gnumed.wxpython import gmNarrativeWorkflows
97 from Gnumed.wxpython import gmPhraseWheel
98 from Gnumed.wxpython import gmMedicationWidgets
99 from Gnumed.wxpython import gmStaffWidgets
100 from Gnumed.wxpython import gmDocumentWidgets
101 from Gnumed.wxpython import gmTimer
102 from Gnumed.wxpython import gmMeasurementWidgets
103 from Gnumed.wxpython import gmFormWidgets
104 from Gnumed.wxpython import gmSnellen
105 from Gnumed.wxpython import gmVaccWidgets
106 from Gnumed.wxpython import gmPersonContactWidgets
107 from Gnumed.wxpython import gmI18nWidgets
108 from Gnumed.wxpython import gmCodingWidgets
109 from Gnumed.wxpython import gmOrganizationWidgets
110 from Gnumed.wxpython import gmAuthWidgets
111 from Gnumed.wxpython import gmFamilyHistoryWidgets
112 from Gnumed.wxpython import gmDataPackWidgets
113 from Gnumed.wxpython import gmContactWidgets
114 from Gnumed.wxpython import gmAddressWidgets
115 from Gnumed.wxpython import gmBillingWidgets
116 from Gnumed.wxpython import gmKeywordExpansionWidgets
117 from Gnumed.wxpython import gmAccessPermissionWidgets
118 from Gnumed.wxpython import gmPraxisWidgets
119 from Gnumed.wxpython import gmEncounterWidgets
120 from Gnumed.wxpython import gmAutoHintWidgets
121 from Gnumed.wxpython import gmPregWidgets
122 from Gnumed.wxpython import gmExternalCareWidgets
123 from Gnumed.wxpython import gmHabitWidgets
124 from Gnumed.wxpython import gmSubstanceMgmtWidgets
125 from Gnumed.wxpython import gmATCWidgets
126 from Gnumed.wxpython import gmLOINCWidgets
127 from Gnumed.wxpython import gmVisualProgressNoteWidgets
128 from Gnumed.wxpython import gmHospitalStayWidgets
129 from Gnumed.wxpython import gmProcedureWidgets
130
131
132 try:
133 _('dummy-no-need-to-translate-but-make-epydoc-happy')
134 except NameError:
135 _ = lambda x:x
136
137 _provider = None
138 _scripting_listener = None
139 _original_wxEndBusyCursor = None
146
147 __wxlog = cLog_wx2gm()
148 _log.info('redirecting wx.Log to [%s]', __wxlog)
149 wx.Log.SetActiveTarget(__wxlog)
154 """GNUmed client's main windows frame.
155
156 This is where it all happens. Avoid popping up any other windows.
157 Most user interaction should happen to and from widgets within this frame
158 """
159
160 - def __init__(self, parent, id, title, size=wx.DefaultSize):
161 """You'll have to browse the source to understand what the constructor does
162 """
163 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
164
165 self.__setup_font()
166
167 self.__gb = gmGuiBroker.GuiBroker()
168 self.__pre_exit_callbacks = []
169 self.bar_width = -1
170 self.menu_id2plugin = {}
171
172 _log.info('workplace is >>>%s<<<', gmPraxis.gmCurrentPraxisBranch().active_workplace)
173
174 self.__setup_main_menu()
175 self.setup_statusbar()
176 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
177 gmTools.coalesce(_provider['title'], ''),
178 _provider['firstnames'][:1],
179 _provider['lastnames'],
180 _provider['short_alias'],
181 _provider['db_user']
182 ))
183
184 self.__set_window_title_template()
185 self.__update_window_title()
186
187
188
189
190
191 self.SetIcon(gmTools.get_icon(wx = wx))
192
193 self.__register_events()
194
195 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
196 self.vbox = wx.BoxSizer(wx.VERTICAL)
197 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
198
199 self.SetAutoLayout(True)
200 self.SetSizerAndFit(self.vbox)
201
202
203
204
205
206 self.__set_GUI_size()
207
208
210
211 font = self.GetFont()
212 _log.debug('system default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
213
214 desired_font_face = _cfg.get (
215 group = 'workplace',
216 option = 'client font',
217 source_order = [
218 ('explicit', 'return'),
219 ('workbase', 'return'),
220 ('local', 'return'),
221 ('user', 'return'),
222 ('system', 'return')
223 ]
224 )
225
226 fonts2try = []
227 if desired_font_face is not None:
228 _log.info('client is configured to use font [%s]', desired_font_face)
229 fonts2try.append(desired_font_face)
230
231 if wx.Platform == '__WXMSW__':
232 sane_font_face = 'Noto Sans'
233 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face)
234 fonts2try.append(sane_font_face)
235 sane_font_face = 'DejaVu Sans'
236 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face)
237 fonts2try.append(sane_font_face)
238
239 if len(fonts2try) == 0:
240 return
241
242 for font_face in fonts2try:
243 success = font.SetFaceName(font_face)
244 if success:
245 self.SetFont(font)
246 _log.debug('switched font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
247 return
248 font = self.GetFont()
249 _log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face)
250
251 return
252
253
255 """Try to get previous window size from backend."""
256
257 cfg = gmCfg.cCfgSQL()
258
259
260 width = int(cfg.get2 (
261 option = 'main.window.width',
262 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
263 bias = 'workplace',
264 default = 800
265 ))
266
267
268 height = int(cfg.get2 (
269 option = 'main.window.height',
270 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
271 bias = 'workplace',
272 default = 600
273 ))
274
275 dw = wx.DisplaySize()[0]
276 dh = wx.DisplaySize()[1]
277
278 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
279 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
280 _log.debug('previous GUI size [%s:%s]', width, height)
281
282
283 if width > dw:
284 _log.debug('adjusting GUI width from %s to %s', width, dw)
285 width = dw
286
287 if height > dh:
288 _log.debug('adjusting GUI height from %s to %s', height, dh)
289 height = dh
290
291
292 if width < 100:
293 _log.debug('adjusting GUI width to minimum of 100 pixel')
294 width = 100
295 if height < 100:
296 _log.debug('adjusting GUI height to minimum of 100 pixel')
297 height = 100
298
299 _log.info('setting GUI to size [%s:%s]', width, height)
300
301 self.SetClientSize(wx.Size(width, height))
302
303
305 """Create the main menu entries.
306
307 Individual entries are farmed out to the modules.
308
309 menu item template:
310
311 item = menu_*.Append(-1)
312 self.Bind(wx.EVT_MENU, self.__on_*, item)
313 """
314 global wx
315 self.mainmenu = wx.MenuBar()
316 self.__gb['main.mainmenu'] = self.mainmenu
317
318
319 menu_gnumed = wx.Menu()
320 self.menu_plugins = wx.Menu()
321 menu_gnumed.Append(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
322 item = menu_gnumed.Append(-1, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
323 self.Bind(wx.EVT_MENU, self.__on_check_for_updates, item)
324 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
325 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
326 menu_gnumed.AppendSeparator()
327
328
329 menu_config = wx.Menu()
330
331 item = menu_config.Append(-1, _('All options'), _('List all options as configured in the database.'))
332 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
333
334
335 menu_cfg_db = wx.Menu()
336 item = menu_cfg_db.Append(-1, _('Language'), _('Configure the database language'))
337 self.Bind(wx.EVT_MENU, self.__on_configure_db_lang, item)
338 item = menu_cfg_db.Append(-1, _('Welcome message'), _('Configure the database welcome message (all users).'))
339 self.Bind(wx.EVT_MENU, self.__on_configure_db_welcome, item)
340 menu_config.Append(wx.NewId(), _('Database ...'), menu_cfg_db)
341
342
343 menu_cfg_client = wx.Menu()
344 item = menu_cfg_client.Append(-1, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
345 self.Bind(wx.EVT_MENU, self.__on_configure_export_chunk_size, item)
346 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
347 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
348 menu_config.Append(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
349
350
351 menu_cfg_ui = wx.Menu()
352 item = menu_cfg_ui.Append(-1, _('Medication measurements'), _('Select the measurements panel to show in the medications plugin.'))
353 self.Bind(wx.EVT_MENU, self.__on_cfg_meds_lab_pnl, item)
354 item = menu_cfg_ui.Append(-1, _('General measurements'), _('Select the measurements panel to show in the top pane.'))
355 self.Bind(wx.EVT_MENU, self.__on_cfg_top_lab_pnl, item)
356
357
358 menu_cfg_doc = wx.Menu()
359 item = menu_cfg_doc.Append(-1, _('Review dialog'), _('Configure review dialog after document display.'))
360 self.Bind(wx.EVT_MENU, self.__on_configure_doc_review_dialog, item)
361 item = menu_cfg_doc.Append(-1, _('UUID display'), _('Configure unique ID dialog on document import.'))
362 self.Bind(wx.EVT_MENU, self.__on_configure_doc_uuid_dialog, item)
363 item = menu_cfg_doc.Append(-1, _('Empty documents'), _('Whether to allow saving documents without parts.'))
364 self.Bind(wx.EVT_MENU, self.__on_configure_partless_docs, item)
365 item = menu_cfg_doc.Append(-1, _('Generate UUID'), _('Whether to generate UUIDs for new documents.'))
366 self.Bind(wx.EVT_MENU, self.__on_configure_generate_doc_uuid, item)
367 menu_cfg_ui.Append(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
368
369
370 menu_cfg_update = wx.Menu()
371 item = menu_cfg_update.Append(-1, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
372 self.Bind(wx.EVT_MENU, self.__on_configure_update_check, item)
373 item = menu_cfg_update.Append(-1, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
374 self.Bind(wx.EVT_MENU, self.__on_configure_update_check_scope, item)
375 item = menu_cfg_update.Append(-1, _('URL'), _('The URL to retrieve version information from.'))
376 self.Bind(wx.EVT_MENU, self.__on_configure_update_url, item)
377 menu_cfg_ui.Append(wx.NewId(), _('Update handling ...'), menu_cfg_update)
378
379
380 menu_cfg_pat_search = wx.Menu()
381 item = menu_cfg_pat_search.Append(-1, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
382 self.Bind(wx.EVT_MENU, self.__on_configure_dob_reminder_proximity, item)
383 item = menu_cfg_pat_search.Append(-1, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
384 self.Bind(wx.EVT_MENU, self.__on_configure_quick_pat_search, item)
385 item = menu_cfg_pat_search.Append(-1, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
386 self.Bind(wx.EVT_MENU, self.__on_configure_initial_pat_plugin, item)
387 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default region for person creation.'))
388 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
389 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
390 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
391 menu_cfg_ui.Append(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
392
393
394 menu_cfg_soap_editing = wx.Menu()
395 item = menu_cfg_soap_editing.Append(-1, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
396 self.Bind(wx.EVT_MENU, self.__on_allow_multiple_new_episodes, item)
397 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.'))
398 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item)
399 item = menu_cfg_soap_editing.Append(-1, _('SOAP fields'), _('Configure SOAP editor - individual SOAP fields vs text editor like'))
400 self.Bind(wx.EVT_MENU, self.__on_use_fields_in_soap_editor, item)
401 menu_cfg_ui.Append(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
402
403
404 menu_cfg_ext_tools = wx.Menu()
405
406
407 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
408 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
409 item = menu_cfg_ext_tools.Append(-1, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
410 self.Bind(wx.EVT_MENU, self.__on_configure_ooo_settle_time, item)
411 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
412 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
413 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
414 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
415
416
417 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
418 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
419 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
420 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
421 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
422 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
423 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
424 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
425 menu_config.Append(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
426
427
428 menu_cfg_bill = wx.Menu()
429 item = menu_cfg_bill.Append(-1, _('Invoice template (no VAT)'), _('Select the template for printing an invoice without VAT.'))
430 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_template_no_vat, item)
431 item = menu_cfg_bill.Append(-1, _('Invoice template (with VAT)'), _('Select the template for printing an invoice with VAT.'))
432 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_template_with_vat, item)
433 item = menu_cfg_bill.Append(-1, _('Catalogs URL'), _('URL for billing catalogs (schedules of fees).'))
434 self.Bind(wx.EVT_MENU, self.__on_configure_billing_catalogs_url, item)
435
436
437 menu_cfg_emr = wx.Menu()
438 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
439 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
440 item = menu_cfg_emr.Append(-1, _('Prescription mode'), _('Select the default mode for creating a prescription.'))
441 self.Bind(wx.EVT_MENU, self.__on_cfg_prescription_mode, item)
442 item = menu_cfg_emr.Append(-1, _('Prescription template'), _('Select the template for printing a prescription.'))
443 self.Bind(wx.EVT_MENU, self.__on_cfg_prescription_template, item)
444 item = menu_cfg_emr.Append(-1, _('Default Gnuplot template'), _('Select the default template for plotting test results.'))
445 self.Bind(wx.EVT_MENU, self.__on_cfg_default_gnuplot_template, item)
446 item = menu_cfg_emr.Append(-1, _('Fallback provider'), _('Select the doctor to fall back to for patients without a primary provider.'))
447 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
448
449
450 menu_cfg_encounter = wx.Menu()
451 item = menu_cfg_encounter.Append(-1, _('Edit before patient change'), _('Edit encounter details before change of patient.'))
452 self.Bind(wx.EVT_MENU, self.__on_cfg_enc_pat_change, item)
453 item = menu_cfg_encounter.Append(-1, _('Minimum duration'), _('Minimum duration of an encounter.'))
454 self.Bind(wx.EVT_MENU, self.__on_cfg_enc_min_ttl, item)
455 item = menu_cfg_encounter.Append(-1, _('Maximum duration'), _('Maximum duration of an encounter.'))
456 self.Bind(wx.EVT_MENU, self.__on_cfg_enc_max_ttl, item)
457 item = menu_cfg_encounter.Append(-1, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
458 self.Bind(wx.EVT_MENU, self.__on_cfg_enc_empty_ttl, item)
459 item = menu_cfg_encounter.Append(-1, _('Default type'), _('Default type for new encounters.'))
460 self.Bind(wx.EVT_MENU, self.__on_cfg_enc_default_type, item)
461 menu_cfg_emr.Append(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
462
463
464 menu_cfg_episode = wx.Menu()
465 item = menu_cfg_episode.Append(-1, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
466 self.Bind(wx.EVT_MENU, self.__on_cfg_epi_ttl, item)
467 menu_cfg_emr.Append(wx.NewId(), _('Episode ...'), menu_cfg_episode)
468
469 menu_config.Append(wx.NewId(), _('User interface ...'), menu_cfg_ui)
470 menu_config.Append(wx.NewId(), _('EMR ...'), menu_cfg_emr)
471 menu_config.Append(wx.NewId(), _('Billing ...'), menu_cfg_bill)
472 menu_gnumed.Append(wx.NewId(), _('Preferences ...'), menu_config)
473
474
475 menu_master_data = wx.Menu()
476 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
477 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
478 item = menu_master_data.Append(-1, _('Manage praxis'), _('Manage your praxis branches.'))
479 self.Bind(wx.EVT_MENU, self.__on_manage_praxis, item)
480 item = menu_master_data.Append(-1, _('Install data packs'), _('Install reference data from data packs.'))
481 self.Bind(wx.EVT_MENU, self.__on_install_data_packs, item)
482 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
483 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
484 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
485 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
486 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
487 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
488 menu_gnumed.Append(wx.NewId(), _('&Master data ...'), menu_master_data)
489
490
491 menu_users = wx.Menu()
492 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
493 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
494 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
495 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
496 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
497 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
498 menu_gnumed.Append(wx.NewId(), _('&Users ...'), menu_users)
499
500 menu_gnumed.AppendSeparator()
501
502 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
503 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
504
505 self.mainmenu.Append(menu_gnumed, '&GNUmed')
506
507
508 menu_person = wx.Menu()
509
510 item = menu_person.Append(-1, _('Search'), _('Search for a person.'))
511 self.Bind(wx.EVT_MENU, self.__on_search_person, item)
512 acc_tab = wx.AcceleratorTable([(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, item.GetId())])
513 self.SetAcceleratorTable(acc_tab)
514 item = menu_person.Append(-1, _('&Register person'), _("Register a new person with GNUmed"))
515 self.Bind(wx.EVT_MENU, self.__on_create_new_patient, item)
516
517 menu_person_import = wx.Menu()
518 item = menu_person_import.Append(-1, _('From &External sources'), _('Load and possibly create person from available external sources.'))
519 self.Bind(wx.EVT_MENU, self.__on_load_external_patient, item)
520 item = menu_person_import.Append(-1, _('&vCard file \u2192 patient'), _('Import demographics from .vcf vCard file as patient'))
521 self.Bind(wx.EVT_MENU, self.__on_import_vcard_from_file, item)
522 item = menu_person_import.Append(-1, _('Clipboard (&XML) \u2192 patient'), _('Import demographics from clipboard (LinuxMedNews XML) as patient'))
523 self.Bind(wx.EVT_MENU, self.__on_import_xml_linuxmednews, item)
524 item = menu_person_import.Append(-1, _('Clipboard (&vCard) \u2192 patient'), _('Import demographics from clipboard (vCard) as patient'))
525 self.Bind(wx.EVT_MENU, self.__on_import_vcard_from_clipboard, item)
526 menu_person.Append(wx.NewId(), '&Import\u2026', menu_person_import)
527
528 menu_person_export = wx.Menu()
529 menu_person_export_clipboard = wx.Menu()
530 item = menu_person_export_clipboard.Append(-1, '&GDT', _('Export demographics of currently active person as GDT into clipboard.'))
531 self.Bind(wx.EVT_MENU, self.__on_export_gdt2clipboard, item)
532 item = menu_person_export_clipboard.Append(-1, '&XML (LinuxMedNews)', _('Export demographics of currently active person as XML (LinuxMedNews) into clipboard'))
533 self.Bind(wx.EVT_MENU, self.__on_export_linuxmednews_xml2clipboard, item)
534 item = menu_person_export_clipboard.Append(-1, '&vCard', _('Export demographics of currently active person as vCard into clipboard'))
535 self.Bind(wx.EVT_MENU, self.__on_export_vcard2clipboard, item)
536 menu_person_export.Append(wx.NewId(), _('\u2192 &Clipboard as\u2026'), menu_person_export_clipboard)
537
538 menu_person_export_file = wx.Menu()
539 item = menu_person_export_file.Append(-1, '&GDT', _('Export demographics of currently active person into GDT file.'))
540 self.Bind(wx.EVT_MENU, self.__on_export_as_gdt, item)
541 item = menu_person_export_file.Append(-1, '&vCard', _('Export demographics of currently active person into vCard file.'))
542 self.Bind(wx.EVT_MENU, self.__on_export_as_vcard, item)
543 menu_person_export.Append(wx.NewId(), _('\u2192 &File as\u2026'), menu_person_export_file)
544
545 menu_person.Append(wx.NewId(), 'E&xport\u2026', menu_person_export)
546
547 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
548 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
549 item = menu_person.Append(-1, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
550 self.Bind(wx.EVT_MENU, self.__on_delete_patient, item)
551 menu_person.AppendSeparator()
552 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
553 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
554 item = menu_person.Append(-1, _('Enlist as user'), _('Enlist current person as GNUmed user'))
555 self.Bind(wx.EVT_MENU, self.__on_enlist_patient_as_staff, item)
556 menu_person.AppendSeparator()
557
558 self.mainmenu.Append(menu_person, '&Person')
559 self.__gb['main.patientmenu'] = menu_person
560
561
562 menu_emr = wx.Menu()
563
564
565 menu_emr_manage = wx.Menu()
566 item = menu_emr_manage.Append(-1, _('&Past history (health issue / PMH)'), _('Add a past/previous medical history item (health issue) to the EMR of the active patient'))
567 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
568 item = menu_emr_manage.Append(-1, _('&Episode'), _('Add an episode of illness to the EMR of the active patient'))
569 self.Bind(wx.EVT_MENU, self.__on_add_episode, item)
570 item = menu_emr_manage.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
571 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
572 item = menu_emr_manage.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
573 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
574 item = menu_emr_manage.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
575 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
576 item = menu_emr_manage.Append(-1, _('&Hospitalizations'), _('Manage hospitalizations.'))
577 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
578 item = menu_emr_manage.Append(-1, _('&External care'), _('Manage external care.'))
579 self.Bind(wx.EVT_MENU, self.__on_manage_external_care, item)
580 item = menu_emr_manage.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
581 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
582 item = menu_emr_manage.Append(-1, _('&Measurements'), _('Manage measurement results for the current patient.'))
583 self.Bind(wx.EVT_MENU, self.__on_manage_measurements, item)
584 item = menu_emr_manage.Append(-1, _('&Vaccinations: by shot'), _('Manage vaccinations for the current patient (by shots given).'))
585 self.Bind(wx.EVT_MENU, self.__on_manage_vaccination, item)
586 item = menu_emr_manage.Append(-1, _('&Vaccinations: by indication'), _('Manage vaccinations for the current patient (by indication).'))
587 self.Bind(wx.EVT_MENU, self.__on_show_all_vaccinations_by_indication, item)
588 item = menu_emr_manage.Append(-1, _('&Vaccinations: latest'), _('List latest vaccinations for the current patient.'))
589 self.Bind(wx.EVT_MENU, self.__on_show_latest_vaccinations, item)
590 item = menu_emr_manage.Append(-1, _('&Family history (FHx)'), _('Manage family history.'))
591 self.Bind(wx.EVT_MENU, self.__on_manage_fhx, item)
592 item = menu_emr_manage.Append(-1, _('&Encounters'), _('List all encounters including empty ones.'))
593 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
594 item = menu_emr_manage.Append(-1, _('&Pregnancy'), _('Calculate EDC.'))
595 self.Bind(wx.EVT_MENU, self.__on_calc_edc, item)
596 item = menu_emr_manage.Append(-1, _('Suppressed hints'), _('Manage dynamic hints suppressed in this patient.'))
597 self.Bind(wx.EVT_MENU, self.__on_manage_suppressed_hints, item)
598 item = menu_emr_manage.Append(-1, _('Substance abuse'), _('Manage substance abuse documentation of this patient.'))
599 self.Bind(wx.EVT_MENU, self.__on_manage_substance_abuse, item)
600 menu_emr.Append(wx.NewId(), _('&Manage ...'), menu_emr_manage)
601
602
603 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
604 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
605
606 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
607 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
608
609
610
611
612 item = menu_emr.Append(-1, _('Statistics'), _('Show a high-level statistic summary of the EMR.'))
613 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
614
615
616
617
618 menu_emr.AppendSeparator()
619
620
621 menu_emr_export = wx.Menu()
622 item = menu_emr_export.Append(-1, _('Journal (encounters)'), _("Copy EMR of the active patient as a chronological journal into export area"))
623 self.Bind(wx.EVT_MENU, self.__on_export_emr_as_journal, item)
624 item = menu_emr_export.Append(-1, _('Journal (mod time)'), _("Copy EMR of active patient as journal (by last modification time) into export area"))
625 self.Bind(wx.EVT_MENU, self.__on_export_emr_by_last_mod, item)
626 item = menu_emr_export.Append(-1, _('Text document'), _("Copy EMR of active patient as text document into export area"))
627 self.Bind(wx.EVT_MENU, self.__export_emr_as_textfile, item)
628 item = menu_emr_export.Append(-1, _('Timeline file'), _("Copy EMR of active patient as timeline file (XML) into export area"))
629 self.Bind(wx.EVT_MENU, self.__export_emr_as_timeline_xml, item)
630 item = menu_emr_export.Append(-1, _('Care structure'), _("Copy EMR of active patient as care structure text file into export area"))
631 self.Bind(wx.EVT_MENU, self.__export_emr_as_care_structure, item)
632
633 item = menu_emr_export.Append(-1, _('MEDISTAR import format (as file)'), _("GNUmed -> MEDISTAR. Save progress notes of active patient's active encounter into a text file."))
634 self.Bind(wx.EVT_MENU, self.__on_export_for_medistar, item)
635 menu_emr.Append(wx.NewId(), _('Put into export area as ...'), menu_emr_export)
636
637 menu_emr.AppendSeparator()
638
639 self.mainmenu.Append(menu_emr, _("&EMR"))
640 self.__gb['main.emrmenu'] = menu_emr
641
642
643 menu_paperwork = wx.Menu()
644 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
645 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
646 item = menu_paperwork.Append(-1, _('Screenshot -> export area'), _('Put a screenshot into the patient export area.'))
647 self.Bind(wx.EVT_MENU, self.__on_save_screenshot_into_export_area, item)
648 menu_paperwork.AppendSeparator()
649 item = menu_paperwork.Append(-1, _('List Placeholders'), _('Show a list of all placeholders.'))
650 self.Bind(wx.EVT_MENU, self.__on_show_placeholders, item)
651
652
653 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
654 self.__gb['main.paperworkmenu'] = menu_paperwork
655
656
657 self.menu_tools = wx.Menu()
658 item = self.menu_tools.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
659 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
660 viewer = _('no viewer installed')
661 if gmShellAPI.detect_external_binary(binary = 'ginkgocadx')[0]:
662 viewer = 'Ginkgo CADx'
663 elif os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
664 viewer = 'OsiriX'
665 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
666 viewer = 'Aeskulap'
667 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
668 viewer = 'AMIDE'
669 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
670 viewer = 'DicomScope'
671 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
672 viewer = '(x)medcon'
673 item = self.menu_tools.Append(-1, _('DICOM viewer'), _('Start DICOM viewer (%s) for CD-ROM (X-Ray, CT, MR, etc). On Windows just insert CD.') % viewer)
674 self.Bind(wx.EVT_MENU, self.__on_dicom_viewer, item)
675 if viewer == _('no viewer installed'):
676 _log.info('neither of Ginkgo CADx / OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
677 self.menu_tools.Enable(id = item.Id, enable=False)
678
679
680 item = self.menu_tools.Append(-1, _('Snellen chart'), _('Display fullscreen snellen chart.'))
681 self.Bind(wx.EVT_MENU, self.__on_snellen, item)
682 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
683 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
684 item = self.menu_tools.Append(-1, 'arriba', _('arriba: cardiovascular risk assessment (%s).') % 'www.arriba-hausarzt.de')
685 self.Bind(wx.EVT_MENU, self.__on_arriba, item)
686 if not gmShellAPI.detect_external_binary(binary = 'arriba')[0]:
687 _log.info('<arriba> not found, disabling "arriba" menu item')
688 self.menu_tools.Enable(id = item.Id, enable = False)
689
690 menu_lab = wx.Menu()
691 item = menu_lab.Append(-1, _('Show HL7'), _('Show formatted data from HL7 file'))
692 self.Bind(wx.EVT_MENU, self.__on_show_hl7, item)
693 item = menu_lab.Append(-1, _('Unwrap XML'), _('Unwrap HL7 data from XML file (Excelleris, ...)'))
694 self.Bind(wx.EVT_MENU, self.__on_unwrap_hl7_from_xml, item)
695 item = menu_lab.Append(-1, _('Stage HL7'), _('Stage HL7 data from file'))
696 self.Bind(wx.EVT_MENU, self.__on_stage_hl7, item)
697 item = menu_lab.Append(-1, _('Browse pending'), _('Browse pending (staged) incoming data'))
698 self.Bind(wx.EVT_MENU, self.__on_incoming, item)
699
700 self.menu_tools.Append(wx.NewId(), _('Lab results ...'), menu_lab)
701
702 self.menu_tools.AppendSeparator()
703
704 self.mainmenu.Append(self.menu_tools, _("&Tools"))
705 self.__gb['main.toolsmenu'] = self.menu_tools
706
707
708 menu_knowledge = wx.Menu()
709
710
711 menu_drug_dbs = wx.Menu()
712 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
713 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
714
715
716
717 item = menu_drug_dbs.Append(-1, 'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
718 self.Bind(wx.EVT_MENU, self.__on_kompendium_ch, item)
719 menu_knowledge.Append(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
720
721
722
723 item = menu_knowledge.Append(-1, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
724 self.Bind(wx.EVT_MENU, self.__on_medical_links, item)
725
726 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
727 self.__gb['main.knowledgemenu'] = menu_knowledge
728
729
730 self.menu_office = wx.Menu()
731
732 item = self.menu_office.Append(-1, _('&Audit trail'), _('Display database audit trail.'))
733 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
734
735 self.menu_office.AppendSeparator()
736
737 item = self.menu_office.Append(-1, _('&Bills'), _('List all bills across all patients.'))
738 self.Bind(wx.EVT_MENU, self.__on_show_all_bills, item)
739
740 item = self.menu_office.Append(-1, _('&Organizations'), _('Manage organizations.'))
741 self.Bind(wx.EVT_MENU, self.__on_manage_orgs, item)
742
743 self.mainmenu.Append(self.menu_office, _('&Office'))
744 self.__gb['main.officemenu'] = self.menu_office
745
746
747 help_menu = wx.Menu()
748 help_menu.Append(-1, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
749 self.Bind(wx.EVT_MENU, self.__on_display_wiki, item)
750 help_menu.Append(-1, _('User manual (www)'), _('Go to the User Manual on the web.'))
751 self.Bind(wx.EVT_MENU, self.__on_display_user_manual_online, item)
752 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
753 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
754 item = help_menu.Append(-1, _('&Clear status line'), _('Clear out the status line.'))
755 self.Bind(wx.EVT_MENU, self.__on_clear_status_line, item)
756 item = help_menu.Append(-1, _('Browse work dir'), _('Browse user working directory [%s].') % os.path.join(gmTools.gmPaths().home_dir, 'gnumed'))
757 self.Bind(wx.EVT_MENU, self.__on_browse_work_dir, item)
758
759 menu_debugging = wx.Menu()
760 item = menu_debugging.Append(-1, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
761 self.Bind(wx.EVT_MENU, self.__on_save_screenshot, item)
762 item = menu_debugging.Append(-1, _('Show log file'), _('Show log file in text viewer.'))
763 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
764 item = menu_debugging.Append(-1, _('Backup log file'), _('Backup content of the log to another file.'))
765 self.Bind(wx.EVT_MENU, self.__on_backup_log_file, item)
766 item = menu_debugging.Append(-1, _('Email log file'), _('Send log file to the authors for help.'))
767 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
768 item = menu_debugging.Append(-1, _('Browse tmp dir'), _('Browse temporary directory [%s].') % gmTools.gmPaths().tmp_dir)
769 self.Bind(wx.EVT_MENU, self.__on_browse_tmp_dir, item)
770 item = menu_debugging.Append(-1, _('Browse internal work dir'), _('Browse internal working directory [%s].') % os.path.join(gmTools.gmPaths().home_dir, '.gnumed'))
771 self.Bind(wx.EVT_MENU, self.__on_browse_internal_work_dir, item)
772 item = menu_debugging.Append(-1, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
773 self.Bind(wx.EVT_MENU, self.__on_display_bugtracker, item)
774 item = menu_debugging.Append(-1, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
775 self.Bind(wx.EVT_MENU, self.__on_unblock_cursor, item)
776 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
777 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
778
779
780 if _cfg.get(option = 'debug'):
781 item = menu_debugging.Append(-1, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
782 self.Bind(wx.EVT_MENU, self.__on_toggle_patient_lock, item)
783 item = menu_debugging.Append(-1, _('Test error handling'), _('Throw an exception to test error handling.'))
784 self.Bind(wx.EVT_MENU, self.__on_test_exception, item)
785 item = menu_debugging.Append(-1, _('Test access violation exception'), _('Simulate an access violation exception.'))
786 self.Bind(wx.EVT_MENU, self.__on_test_access_violation, item)
787 item = menu_debugging.Append(-1, _('Test access checking'), _('Simulate a failing access check.'))
788 self.Bind(wx.EVT_MENU, self.__on_test_access_checking, item)
789 item = menu_debugging.Append(-1, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
790 self.Bind(wx.EVT_MENU, self.__on_invoke_inspector, item)
791 try:
792 import wx.lib.inspection
793 except ImportError:
794 menu_debugging.Enable(id = ID, enable = False)
795 try:
796 import faulthandler
797 item = menu_debugging.Append(-1, _('Test fault handler'), _('Simulate a catastrophic fault (SIGSEGV).'))
798 self.Bind(wx.EVT_MENU, self.__on_test_segfault, item)
799 except ImportError:
800 pass
801 item = menu_debugging.Append(-1, _('Test placeholder'), _('Manually test placeholders'))
802 self.Bind(wx.EVT_MENU, self.__on_test_placeholders, item)
803
804 help_menu.Append(wx.NewId(), _('Debugging ...'), menu_debugging)
805 help_menu.AppendSeparator()
806
807 item = help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), '')
808 self.Bind(wx.EVT_MENU, self.OnAbout, item)
809 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
810 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
811 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors'))
812 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item)
813 help_menu.AppendSeparator()
814
815 self.mainmenu.Append(help_menu, _("&Help"))
816
817 self.__gb['main.helpmenu'] = help_menu
818
819
820 self.SetMenuBar(self.mainmenu)
821
822
825
826
827
829 """register events we want to react to"""
830
831 self.Bind(wx.EVT_CLOSE, self.OnClose)
832 self.Bind(wx.EVT_QUERY_END_SESSION, self._on_query_end_session)
833 self.Bind(wx.EVT_END_SESSION, self._on_end_session)
834
835 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
836 gmDispatcher.connect(signal = 'statustext', receiver = self._on_set_statustext)
837 gmDispatcher.connect(signal = 'request_user_attention', receiver = self._on_request_user_attention)
838 gmDispatcher.connect(signal = 'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
839 gmDispatcher.connect(signal = 'plugin_loaded', receiver = self._on_plugin_loaded)
840
841 gmDispatcher.connect(signal = 'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
842 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
843
844
845
846 gmPerson.gmCurrentPatient().register_before_switching_from_patient_callback(callback = self._before_switching_from_patient_callback)
847
848
850
851 if kwds['table'] == 'dem.praxis_branch':
852 if kwds['operation'] != 'UPDATE':
853 return True
854 branch = gmPraxis.gmCurrentPraxisBranch()
855 if branch['pk_praxis_branch'] != kwds['pk_row']:
856 return True
857 self.__update_window_title()
858 return True
859
860 if kwds['table'] == 'dem.names':
861 pat = gmPerson.gmCurrentPatient()
862 if pat.connected:
863 if pat.ID != kwds['pk_identity']:
864 return True
865 self.__update_window_title()
866 return True
867
868 if kwds['table'] == 'dem.identity':
869 if kwds['operation'] != 'UPDATE':
870 return True
871 pat = gmPerson.gmCurrentPatient()
872 if pat.connected:
873 if pat.ID != kwds['pk_identity']:
874 return True
875 self.__update_window_title()
876 return True
877
878 return True
879
880
881 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
882
883 _log.debug('registering plugin with menu system')
884 _log.debug(' generic name: %s', plugin_name)
885 _log.debug(' class name: %s', class_name)
886 _log.debug(' specific menu: %s', menu_name)
887 _log.debug(' menu item: %s', menu_item_name)
888
889
890 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
891 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
892 self.menu_id2plugin[item.Id] = class_name
893
894
895 if menu_name is not None:
896 menu = self.__gb['main.%smenu' % menu_name]
897 item = menu.Append(-1, menu_item_name, menu_help_string)
898 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
899 self.menu_id2plugin[item.Id] = class_name
900
901 return True
902
908
910 wx.Bell()
911 wx.Bell()
912 wx.Bell()
913 _log.warning('unhandled event detected: QUERY_END_SESSION')
914 _log.info('we should be saving ourselves from here')
915 gmLog2.flush()
916 print('unhandled event detected: QUERY_END_SESSION')
917
919 wx.Bell()
920 wx.Bell()
921 wx.Bell()
922 _log.warning('unhandled event detected: END_SESSION')
923 gmLog2.flush()
924 print('unhandled event detected: END_SESSION')
925
927 if not callable(callback):
928 raise TypeError('callback [%s] not callable' % callback)
929
930 self.__pre_exit_callbacks.append(callback)
931
932 - def _on_set_statustext_pubsub(self, context=None):
933 msg = '%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
934 wx.CallAfter(self.SetStatusText, msg)
935
936 try:
937 if context.data['beep']:
938 wx.Bell()
939 except KeyError:
940 pass
941
942 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
943
944 if msg is None:
945 msg = _('programmer forgot to specify status message')
946
947 if loglevel is not None:
948 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
949
950 msg = '%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
951 wx.CallAfter(self.SetStatusText, msg)
952
953 if beep:
954 wx.Bell()
955
957
958 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
959 wx.Bell()
960 if not wx.GetApp().IsActive():
961 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
962
963 gmHooks.run_hook_script(hook = 'db_maintenance_warning')
964
965 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
966 None,
967 -1,
968 caption = _('Database shutdown warning'),
969 question = _(
970 'The database will be shut down for maintenance\n'
971 'in a few minutes.\n'
972 '\n'
973 'In order to not suffer any loss of data you\n'
974 'will need to save your current work and log\n'
975 'out of this GNUmed client.\n'
976 ),
977 button_defs = [
978 {
979 'label': _('Close now'),
980 'tooltip': _('Close this GNUmed client immediately.'),
981 'default': False
982 },
983 {
984 'label': _('Finish work'),
985 'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
986 'default': True
987 }
988 ]
989 )
990 decision = dlg.ShowModal()
991 if decision == wx.ID_YES:
992 top_win = wx.GetApp().GetTopWindow()
993 wx.CallAfter(top_win.Close)
994
996
997 if not wx.GetApp().IsActive():
998 if urgent:
999 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
1000 else:
1001 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
1002
1003 if msg is not None:
1004 self.SetStatusText(msg)
1005
1006 if urgent:
1007 wx.Bell()
1008
1009 gmHooks.run_hook_script(hook = 'request_user_attention')
1010
1011 - def _on_post_patient_selection(self, **kwargs):
1012 self.__update_window_title()
1013 gmDispatcher.send(signal = 'statustext', msg = '')
1014 try:
1015 gmHooks.run_hook_script(hook = 'post_patient_activation')
1016 except:
1017 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
1018 raise
1019
1027
1028
1029
1032
1039
1040
1044
1045
1054
1055
1069
1070
1071
1072
1074
1075 return
1076
1077
1078 from Gnumed.wxpython import gmAbout
1079 frame_about = gmAbout.AboutFrame (
1080 self,
1081 -1,
1082 _("About GNUmed"),
1083 size=wx.Size(350, 300),
1084 style = wx.MAXIMIZE_BOX,
1085 version = _cfg.get(option = 'client_version'),
1086 debug = _cfg.get(option = 'debug')
1087 )
1088 frame_about.Centre(wx.BOTH)
1089 gmTopLevelFrame.otherWin = frame_about
1090 frame_about.Show(True)
1091 frame_about.Destroy()
1092
1093
1124
1125
1137
1138
1139
1140
1142 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1143 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1144 self.Close(True)
1145 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1146
1147
1150
1151
1153 send = gmGuiHelpers.gm_show_question (
1154 _('This will send a notification about database downtime\n'
1155 'to all GNUmed clients connected to your database.\n'
1156 '\n'
1157 'Do you want to send the notification ?\n'
1158 ),
1159 _('Announcing database maintenance downtime')
1160 )
1161 if not send:
1162 return
1163 gmPG2.send_maintenance_notification()
1164
1165
1168
1169
1170
1183
1184 gmCfgWidgets.configure_string_option (
1185 message = _(
1186 'Some network installations cannot cope with loading\n'
1187 'documents of arbitrary size in one piece from the\n'
1188 'database (mainly observed on older Windows versions)\n.'
1189 '\n'
1190 'Under such circumstances documents need to be retrieved\n'
1191 'in chunks and reassembled on the client.\n'
1192 '\n'
1193 'Here you can set the size (in Bytes) above which\n'
1194 'GNUmed will retrieve documents in chunks. Setting this\n'
1195 'value to 0 will disable the chunking protocol.'
1196 ),
1197 option = 'horstspace.blob_export_chunk_size',
1198 bias = 'workplace',
1199 default_value = 1024 * 1024,
1200 validator = is_valid
1201 )
1202
1203
1204
1272
1276
1277
1278
1287
1288 gmCfgWidgets.configure_string_option (
1289 message = _(
1290 'When GNUmed cannot find an OpenOffice server it\n'
1291 'will try to start one. OpenOffice, however, needs\n'
1292 'some time to fully start up.\n'
1293 '\n'
1294 'Here you can set the time for GNUmed to wait for OOo.\n'
1295 ),
1296 option = 'external.ooo.startup_settle_time',
1297 bias = 'workplace',
1298 default_value = 2.0,
1299 validator = is_valid
1300 )
1301
1304
1305
1320
1321 gmCfgWidgets.configure_string_option (
1322 message = _(
1323 'GNUmed will use this URL to access a website which lets\n'
1324 'you report an adverse drug reaction (ADR).\n'
1325 '\n'
1326 'If you leave this empty it will fall back\n'
1327 'to an URL for reporting ADRs in Germany.'
1328 ),
1329 option = 'external.urls.report_ADR',
1330 bias = 'user',
1331 default_value = german_default,
1332 validator = is_valid
1333 )
1334
1348
1349 gmCfgWidgets.configure_string_option (
1350 message = _(
1351 'GNUmed will use this URL to access a website which lets\n'
1352 'you report an adverse vaccination reaction (vADR).\n'
1353 '\n'
1354 'If you set it to a specific address that URL must be\n'
1355 'accessible now. If you leave it empty it will fall back\n'
1356 'to the URL for reporting other adverse drug reactions.'
1357 ),
1358 option = 'external.urls.report_vaccine_ADR',
1359 bias = 'user',
1360 default_value = german_default,
1361 validator = is_valid
1362 )
1363
1378
1379 gmCfgWidgets.configure_string_option (
1380 message = _(
1381 'GNUmed will use this URL to access an encyclopedia of\n'
1382 'measurement/lab methods from within the measurments grid.\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.measurements_encyclopedia',
1388 bias = 'user',
1389 default_value = german_default,
1390 validator = is_valid
1391 )
1392
1407
1408 gmCfgWidgets.configure_string_option (
1409 message = _(
1410 'GNUmed will use this URL to access a page showing\n'
1411 'vaccination schedules.\n'
1412 '\n'
1413 'You can leave this empty but to set it to a specific\n'
1414 'address the URL must be accessible now.'
1415 ),
1416 option = 'external.urls.vaccination_plans',
1417 bias = 'user',
1418 default_value = german_default,
1419 validator = is_valid
1420 )
1421
1434
1435 gmCfgWidgets.configure_string_option (
1436 message = _(
1437 'Enter the shell command with which to start the\n'
1438 'the ACS risk assessment calculator.\n'
1439 '\n'
1440 'GNUmed will try to verify the path which may,\n'
1441 'however, fail if you are using an emulator such\n'
1442 'as Wine. Nevertheless, starting the calculator\n'
1443 'will work as long as the shell command is correct\n'
1444 'despite the failing test.'
1445 ),
1446 option = 'external.tools.acs_risk_calculator_cmd',
1447 bias = 'user',
1448 validator = is_valid
1449 )
1450
1453
1466
1467 gmCfgWidgets.configure_string_option (
1468 message = _(
1469 'Enter the shell command with which to start\n'
1470 'the FreeDiams drug database frontend.\n'
1471 '\n'
1472 'GNUmed will try to verify that path.'
1473 ),
1474 option = 'external.tools.freediams_cmd',
1475 bias = 'workplace',
1476 default_value = None,
1477 validator = is_valid
1478 )
1479
1492
1493 gmCfgWidgets.configure_string_option (
1494 message = _(
1495 'Enter the shell command with which to start the\n'
1496 'the IFAP drug database.\n'
1497 '\n'
1498 'GNUmed will try to verify the path which may,\n'
1499 'however, fail if you are using an emulator such\n'
1500 'as Wine. Nevertheless, starting IFAP will work\n'
1501 'as long as the shell command is correct despite\n'
1502 'the failing test.'
1503 ),
1504 option = 'external.ifap-win.shell_command',
1505 bias = 'workplace',
1506 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1507 validator = is_valid
1508 )
1509
1510
1511
1560
1561
1562
1579
1582
1585
1590
1591 gmCfgWidgets.configure_string_option (
1592 message = _(
1593 'When a patient is activated GNUmed checks the\n'
1594 "proximity of the patient's birthday.\n"
1595 '\n'
1596 'If the birthday falls within the range of\n'
1597 ' "today %s <the interval you set here>"\n'
1598 'GNUmed will remind you of the recent or\n'
1599 'imminent anniversary.'
1600 ) % '\u2213',
1601 option = 'patient_search.dob_warn_interval',
1602 bias = 'user',
1603 default_value = '1 week',
1604 validator = is_valid
1605 )
1606
1608
1609 gmCfgWidgets.configure_boolean_option (
1610 parent = self,
1611 question = _(
1612 'When adding progress notes do you want to\n'
1613 'allow opening several unassociated, new\n'
1614 'episodes for a patient at once ?\n'
1615 '\n'
1616 'This can be particularly helpful when entering\n'
1617 'progress notes on entirely new patients presenting\n'
1618 'with a multitude of problems on their first visit.'
1619 ),
1620 option = 'horstspace.soap_editor.allow_same_episode_multiple_times',
1621 button_tooltips = [
1622 _('Yes, allow for multiple new episodes concurrently.'),
1623 _('No, only allow editing one new episode at a time.')
1624 ]
1625 )
1626
1628
1629 gmCfgWidgets.configure_boolean_option (
1630 parent = self,
1631 question = _(
1632 'When activating a patient, do you want GNUmed to\n'
1633 'auto-open editors for all active problems that were\n'
1634 'touched upon during the current and the most recent\n'
1635 'encounter ?'
1636 ),
1637 option = 'horstspace.soap_editor.auto_open_latest_episodes',
1638 button_tooltips = [
1639 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1640 _('No, only auto-open one editor for a new, unassociated problem.')
1641 ]
1642 )
1643
1644
1646 gmCfgWidgets.configure_boolean_option (
1647 parent = self,
1648 question = _(
1649 'When editing progress notes, do you want GNUmed to\n'
1650 'show individual fields for each of the SOAP categories\n'
1651 'or do you want to use a text-editor like field for\n'
1652 'all SOAP categories which can then be set per line\n'
1653 'of input ?'
1654 ),
1655 option = 'horstspace.soap_editor.use_one_field_per_soap_category',
1656 button_tooltips = [
1657 _('Yes, show a dedicated field per SOAP category.'),
1658 _('No, use one field for all SOAP categories.')
1659 ]
1660 )
1661
1662
1708
1709
1710
1713
1716
1729
1730 gmCfgWidgets.configure_string_option (
1731 message = _(
1732 'GNUmed will use this URL to let you browse\n'
1733 'billing catalogs (schedules of fees).\n'
1734 '\n'
1735 'You can leave this empty but to set it to a specific\n'
1736 'address the URL must be accessible now.'
1737 ),
1738 option = 'external.urls.schedules_of_fees',
1739 bias = 'user',
1740 default_value = german_default,
1741 validator = is_valid
1742 )
1743
1744
1745
1748
1751
1753 gmCfgWidgets.configure_string_from_list_option (
1754 parent = self,
1755 message = _('Select the default prescription mode.\n'),
1756 option = 'horst_space.default_prescription_mode',
1757 bias = 'user',
1758 default_value = 'form',
1759 choices = [ _('Formular'), _('Datenbank') ],
1760 columns = [_('Prescription mode')],
1761 data = [ 'form', 'database' ]
1762 )
1763
1766
1769
1772
1775
1777 enc_types = gmEMRStructItems.get_encounter_types()
1778 msg = _(
1779 'Select the default type for new encounters.\n'
1780 '\n'
1781 'Leaving this unset will make GNUmed apply the most commonly used type.\n'
1782 )
1783 gmCfgWidgets.configure_string_from_list_option (
1784 parent = self,
1785 message = msg,
1786 option = 'encounter.default_type',
1787 bias = 'user',
1788
1789 choices = [ e[0] for e in enc_types ],
1790 columns = [_('Encounter type')],
1791 data = [ e[1] for e in enc_types ]
1792 )
1793
1795 gmCfgWidgets.configure_boolean_option (
1796 parent = self,
1797 question = _(
1798 'Do you want GNUmed to show the encounter\n'
1799 'details editor when changing the active patient ?'
1800 ),
1801 option = 'encounter.show_editor_before_patient_change',
1802 button_tooltips = [
1803 _('Yes, show the encounter editor if it seems appropriate.'),
1804 _('No, never show the encounter editor even if it would seem useful.')
1805 ]
1806 )
1807
1812
1813 gmCfgWidgets.configure_string_option (
1814 message = _(
1815 'When a patient is activated GNUmed checks the\n'
1816 'chart for encounters lacking any entries.\n'
1817 '\n'
1818 'Any such encounters older than what you set\n'
1819 'here will be removed from the medical record.\n'
1820 '\n'
1821 'To effectively disable removal of such encounters\n'
1822 'set this option to an improbable value.\n'
1823 ),
1824 option = 'encounter.ttl_if_empty',
1825 bias = 'user',
1826 default_value = '1 week',
1827 validator = is_valid
1828 )
1829
1834
1835 gmCfgWidgets.configure_string_option (
1836 message = _(
1837 'When a patient is activated GNUmed checks the\n'
1838 'age of the most recent encounter.\n'
1839 '\n'
1840 'If that encounter is younger than this age\n'
1841 'the existing encounter will be continued.\n'
1842 '\n'
1843 '(If it is really old a new encounter is\n'
1844 ' started, or else GNUmed will ask you.)\n'
1845 ),
1846 option = 'encounter.minimum_ttl',
1847 bias = 'user',
1848 default_value = '1 hour 30 minutes',
1849 validator = is_valid
1850 )
1851
1856
1857 gmCfgWidgets.configure_string_option (
1858 message = _(
1859 'When a patient is activated GNUmed checks the\n'
1860 'age of the most recent encounter.\n'
1861 '\n'
1862 'If that encounter is older than this age\n'
1863 'GNUmed will always start a new encounter.\n'
1864 '\n'
1865 '(If it is very recent the existing encounter\n'
1866 ' is continued, or else GNUmed will ask you.)\n'
1867 ),
1868 option = 'encounter.maximum_ttl',
1869 bias = 'user',
1870 default_value = '6 hours',
1871 validator = is_valid
1872 )
1873
1882
1883 gmCfgWidgets.configure_string_option (
1884 message = _(
1885 'At any time there can only be one open (ongoing)\n'
1886 'episode for each health issue.\n'
1887 '\n'
1888 'When you try to open (add data to) an episode on a health\n'
1889 'issue GNUmed will check for an existing open episode on\n'
1890 'that issue. If there is any it will check the age of that\n'
1891 'episode. The episode is closed if it has been dormant (no\n'
1892 'data added, that is) for the period of time (in days) you\n'
1893 'set here.\n'
1894 '\n'
1895 "If the existing episode hasn't been dormant long enough\n"
1896 'GNUmed will consult you what to do.\n'
1897 '\n'
1898 'Enter maximum episode dormancy in DAYS:'
1899 ),
1900 option = 'episode.ttl',
1901 bias = 'user',
1902 default_value = 60,
1903 validator = is_valid
1904 )
1905
1936
1951
1976
1986
1987 gmCfgWidgets.configure_string_option (
1988 message = _(
1989 'GNUmed can check for new releases being available. To do\n'
1990 'so it needs to load version information from an URL.\n'
1991 '\n'
1992 'The default URL is:\n'
1993 '\n'
1994 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1995 '\n'
1996 'but you can configure any other URL locally. Note\n'
1997 'that you must enter the location as a valid URL.\n'
1998 'Depending on the URL the client will need online\n'
1999 'access when checking for updates.'
2000 ),
2001 option = 'horstspace.update.url',
2002 bias = 'workplace',
2003 default_value = 'http://www.gnumed.de/downloads/gnumed-versions.txt',
2004 validator = is_valid
2005 )
2006
2024
2041
2058
2069
2070 gmCfgWidgets.configure_string_option (
2071 message = _(
2072 'GNUmed can show the document review dialog after\n'
2073 'calling the appropriate viewer for that document.\n'
2074 '\n'
2075 'Select the conditions under which you want\n'
2076 'GNUmed to do so:\n'
2077 '\n'
2078 ' 0: never display the review dialog\n'
2079 ' 1: always display the dialog\n'
2080 ' 2: only if there is no previous review by me\n'
2081 ' 3: only if there is no previous review at all\n'
2082 ' 4: only if there is no review by the responsible reviewer\n'
2083 '\n'
2084 'Note that if a viewer is configured to not block\n'
2085 'GNUmed during document display the review dialog\n'
2086 'will actually appear in parallel to the viewer.'
2087 ),
2088 option = 'horstspace.document_viewer.review_after_display',
2089 bias = 'user',
2090 default_value = 3,
2091 validator = is_valid
2092 )
2093
2095
2096
2097 master_data_lists = [
2098 'adr',
2099 'provinces',
2100 'codes',
2101 'billables',
2102 'ref_data_sources',
2103 'meds_substances',
2104 'meds_doses',
2105 'meds_components',
2106 'meds_drugs',
2107 'meds_vaccines',
2108 'orgs',
2109 'labs',
2110 'meta_test_types',
2111 'test_types',
2112 'test_panels',
2113 'form_templates',
2114 'doc_types',
2115 'enc_types',
2116 'communication_channel_types',
2117 'text_expansions',
2118 'patient_tags',
2119 'hints',
2120 'db_translations',
2121 'workplaces'
2122 ]
2123
2124 master_data_list_names = {
2125 'adr': _('Addresses (likely slow)'),
2126 'hints': _('Dynamic automatic hints'),
2127 'codes': _('Codes and their respective terms'),
2128 'communication_channel_types': _('Communication channel types'),
2129 'orgs': _('Organizations with their units, addresses, and comm channels'),
2130 'labs': _('Measurements: diagnostic organizations (path labs, ...)'),
2131 'test_types': _('Measurements: test types'),
2132 'test_panels': _('Measurements: test panels/profiles/batteries'),
2133 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2134 'doc_types': _('Document types'),
2135 'enc_types': _('Encounter types'),
2136 'text_expansions': _('Keyword based text expansion macros'),
2137 'meta_test_types': _('Measurements: aggregate test types'),
2138 'patient_tags': _('Patient tags'),
2139 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2140 'db_translations': _('String translations in the database'),
2141 'meds_vaccines': _('Medications: vaccines'),
2142 'meds_substances': _('Medications: base substances'),
2143 'meds_doses': _('Medications: substance dosage'),
2144 'meds_components': _('Medications: drug components'),
2145 'meds_drugs': _('Medications: drug products and generic drugs'),
2146 'workplaces': _('Workplace profiles (which plugins to load)'),
2147 'billables': _('Billable items'),
2148 'ref_data_sources': _('Reference data sources')
2149 }
2150
2151 map_list2handler = {
2152 'form_templates': gmFormWidgets.manage_form_templates,
2153 'doc_types': gmDocumentWidgets.manage_document_types,
2154 'text_expansions': gmKeywordExpansionWidgets.configure_keyword_text_expansion,
2155 'db_translations': gmI18nWidgets.manage_translations,
2156 'codes': gmCodingWidgets.browse_coded_terms,
2157 'enc_types': gmEncounterWidgets.manage_encounter_types,
2158 'provinces': gmAddressWidgets.manage_regions,
2159 'workplaces': gmPraxisWidgets.configure_workplace_plugins,
2160 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2161 'test_types': gmMeasurementWidgets.manage_measurement_types,
2162 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2163 'orgs': gmOrganizationWidgets.manage_orgs,
2164 'adr': gmAddressWidgets.manage_addresses,
2165 'meds_substances': gmSubstanceMgmtWidgets.manage_substances,
2166 'meds_doses': gmSubstanceMgmtWidgets.manage_substance_doses,
2167 'meds_components': gmSubstanceMgmtWidgets.manage_drug_components,
2168 'meds_drugs': gmSubstanceMgmtWidgets.manage_drug_products,
2169 'meds_vaccines': gmVaccWidgets.manage_vaccines,
2170 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2171 'communication_channel_types': gmContactWidgets.manage_comm_channel_types,
2172 'billables': gmBillingWidgets.manage_billables,
2173 'ref_data_sources': gmCodingWidgets.browse_data_sources,
2174 'hints': gmAutoHintWidgets.manage_dynamic_hints,
2175 'test_panels': gmMeasurementWidgets.manage_test_panels
2176 }
2177
2178
2179 def edit(item):
2180 try: map_list2handler[item](parent = self)
2181 except KeyError: pass
2182 return False
2183
2184
2185 gmListWidgets.get_choices_from_list (
2186 parent = self,
2187 caption = _('Master data management'),
2188 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2189 data = master_data_lists,
2190 columns = [_('Select the list you want to manage:')],
2191 edit_callback = edit,
2192 single_selection = True,
2193 ignore_OK_button = True
2194 )
2195
2198
2200
2201 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2202 if found:
2203 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2204 return
2205
2206 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2207 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking = False)
2208 return
2209
2210 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2211 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2212 if found:
2213 gmShellAPI.run_command_in_shell(cmd, blocking = False)
2214 return
2215
2216 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2217
2219
2220 curr_pat = gmPerson.gmCurrentPatient()
2221
2222 arriba = gmArriba.cArriba()
2223 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2224 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2225 return
2226
2227
2228 if curr_pat is None:
2229 return
2230
2231 if arriba.pdf_result is None:
2232 return
2233
2234 doc = gmDocumentWidgets.save_file_as_new_document (
2235 parent = self,
2236 filename = arriba.pdf_result,
2237 document_type = _('risk assessment'),
2238 pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit']
2239 )
2240
2241 try: os.remove(arriba.pdf_result)
2242 except Exception: _log.exception('cannot remove [%s]', arriba.pdf_result)
2243
2244 if doc is None:
2245 return
2246
2247 doc['comment'] = 'arriba: %s' % _('cardiovascular risk assessment')
2248 doc.save()
2249
2250 try:
2251 open(arriba.xml_result).close()
2252 part = doc.add_part(file = arriba.xml_result)
2253 except Exception:
2254 _log.exception('error accessing [%s]', arriba.xml_result)
2255 gmDispatcher.send(signal = 'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2256
2257 if part is None:
2258 return
2259
2260 part['obj_comment'] = 'XML-Daten'
2261 part['filename'] = 'arriba-result.xml'
2262 part.save()
2263
2265
2266 dbcfg = gmCfg.cCfgSQL()
2267 cmd = dbcfg.get2 (
2268 option = 'external.tools.acs_risk_calculator_cmd',
2269 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2270 bias = 'user'
2271 )
2272
2273 if cmd is None:
2274 gmDispatcher.send(signal = 'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2275 return
2276
2277 cwd = os.path.expanduser(os.path.join('~', '.gnumed'))
2278 try:
2279 subprocess.check_call (
2280 args = (cmd,),
2281 close_fds = True,
2282 cwd = cwd
2283 )
2284 except (OSError, ValueError, subprocess.CalledProcessError):
2285 _log.exception('there was a problem executing [%s]', cmd)
2286 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2287 return
2288
2289 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2290 for pdf in pdfs:
2291 try:
2292 open(pdf).close()
2293 except:
2294 _log.exception('error accessing [%s]', pdf)
2295 gmDispatcher.send(signal = 'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2296 continue
2297
2298 doc = gmDocumentWidgets.save_file_as_new_document (
2299 parent = self,
2300 filename = pdf,
2301 document_type = 'risk assessment',
2302 pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit']
2303 )
2304
2305 try:
2306 os.remove(pdf)
2307 except Exception:
2308 _log.exception('cannot remove [%s]', pdf)
2309
2310 if doc is None:
2311 continue
2312 doc['comment'] = 'arriba: %s' % _('cardiovascular risk assessment')
2313 doc.save()
2314
2315 return
2316
2317
2325
2328
2331
2334
2351
2352
2355
2356
2362
2363
2366
2367
2368
2369
2372
2373
2376
2377
2380
2381
2382
2383
2385 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2386 self.__save_screenshot_to_file(filename = fname)
2387
2388
2390 raise ValueError('raised ValueError to test exception handling')
2391
2392
2394 import faulthandler
2395 _log.debug('testing faulthandler via SIGSEGV')
2396 faulthandler._sigsegv()
2397
2398
2402
2403
2405 raise gmExceptions.AccessDenied (
2406 _('[-9999]: <access violation test error>'),
2407 source = 'GNUmed code',
2408 code = -9999,
2409 details = _('This is a deliberate AccessDenied exception thrown to test the handling of access violations by means of a decorator.')
2410 )
2411
2412 @gmAccessPermissionWidgets.verify_minimum_required_role('admin', activity = _('testing access check for non-existant <admin> role'))
2414 raise gmExceptions.AccessDenied (
2415 _('[-9999]: <access violation test error>'),
2416 source = 'GNUmed code',
2417 code = -9999,
2418 details = _('This is a deliberate AccessDenied exception. You should not see this message because the role is checked in a decorator.')
2419 )
2420
2422 import wx.lib.inspection
2423 wx.lib.inspection.InspectionTool().Show()
2424
2427
2430
2433
2436
2443
2447
2450
2453
2460
2464
2466 name = os.path.basename(gmLog2._logfile_name)
2467 name, ext = os.path.splitext(name)
2468 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2469 new_path = os.path.expanduser(os.path.join('~', 'gnumed'))
2470
2471 dlg = wx.FileDialog (
2472 parent = self,
2473 message = _("Save current log as..."),
2474 defaultDir = new_path,
2475 defaultFile = new_name,
2476 wildcard = "%s (*.log)|*.log" % _("log files"),
2477 style = wx.FD_SAVE
2478 )
2479 choice = dlg.ShowModal()
2480 new_name = dlg.GetPath()
2481 dlg.Destroy()
2482 if choice != wx.ID_OK:
2483 return True
2484
2485 _log.warning('syncing log file for backup to [%s]', new_name)
2486 gmLog2.flush()
2487 shutil.copy2(gmLog2._logfile_name, new_name)
2488 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2489
2492
2493
2496
2497
2500
2501
2504
2505
2506
2507
2509 """This is the wx.EVT_CLOSE handler.
2510
2511 - framework still functional
2512 """
2513 _log.debug('gmTopLevelFrame.OnClose() start')
2514 self._clean_exit()
2515 self.Destroy()
2516 _log.debug('gmTopLevelFrame.OnClose() end')
2517 return True
2518
2519
2524
2525
2533
2540
2547
2554
2564
2572
2580
2588
2596
2604
2605
2606 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2615
2616
2617 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2626
2627
2628 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2637
2638
2639 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage family history'))
2648
2649 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2656
2657 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('calculate EDC'))
2661
2662
2663 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage suppressed hints'))
2670
2671
2678
2679
2696
2699
2702
2703
2705 pat = gmPerson.gmCurrentPatient()
2706 if not pat.connected:
2707 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR. No active patient.'))
2708 return False
2709 from Gnumed.exporters import gmPatientExporter
2710 exporter = gmPatientExporter.cEmrExport(patient = pat)
2711 fname = gmTools.get_unique_filename(prefix = 'gm-exp-', suffix = '.txt')
2712 output_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace')
2713 exporter.set_output_file(output_file)
2714 exporter.dump_constraints()
2715 exporter.dump_demographic_record(True)
2716 exporter.dump_clinical_record()
2717 exporter.dump_med_docs()
2718 output_file.close()
2719 pat.export_area.add_file(filename = fname, hint = _('EMR as text document'))
2720
2721
2736
2737
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2870
2871
2896
2897
2904
2905
2907 curr_pat = gmPerson.gmCurrentPatient()
2908 if not curr_pat.connected:
2909 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2910 return
2911
2912 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2913 if tag is None:
2914 return
2915
2916 tag = curr_pat.add_tag(tag['pk_tag_image'])
2917 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2918 comment = wx.GetTextFromUser (
2919 message = msg,
2920 caption = _('Editing tag comment'),
2921 default_value = gmTools.coalesce(tag['comment'], ''),
2922 parent = self
2923 )
2924
2925 if comment == '':
2926 return
2927
2928 if comment.strip() == tag['comment']:
2929 return
2930
2931 if comment == ' ':
2932 tag['comment'] = None
2933 else:
2934 tag['comment'] = comment.strip()
2935
2936 tag.save()
2937
2938
2948
2949
2959
2960
2969
2970
2979
2980
2982 curr_pat = gmPerson.gmCurrentPatient()
2983 if not curr_pat.connected:
2984 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2985 return False
2986 enc = 'cp850'
2987 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'current-patient.gdt'))
2988 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2989 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2990
2991
3000
3001
3004
3005
3008
3009
3012
3013
3016
3019
3027
3035
3038
3045
3049
3052
3055
3056
3059
3060
3063
3064
3067
3068
3070 """Cleanup helper.
3071
3072 - should ALWAYS be called when this program is
3073 to be terminated
3074 - ANY code that should be executed before a
3075 regular shutdown should go in here
3076 - framework still functional
3077 """
3078 _log.debug('gmTopLevelFrame._clean_exit() start')
3079
3080
3081 listener = gmBackendListener.gmBackendListener()
3082 try:
3083 listener.shutdown()
3084 except:
3085 _log.exception('cannot stop backend notifications listener thread')
3086
3087
3088 if _scripting_listener is not None:
3089 try:
3090 _scripting_listener.shutdown()
3091 except:
3092 _log.exception('cannot stop scripting listener thread')
3093
3094
3095 self.clock_update_timer.Stop()
3096 gmTimer.shutdown()
3097 gmPhraseWheel.shutdown()
3098
3099
3100 for call_back in self.__pre_exit_callbacks:
3101 try:
3102 call_back()
3103 except:
3104 print('*** pre-exit callback failed ***')
3105 print('%s' % call_back)
3106 _log.exception('callback [%s] failed', call_back)
3107
3108
3109 gmDispatcher.send('application_closing')
3110
3111
3112 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
3113
3114
3115 curr_width, curr_height = self.GetClientSize()
3116 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
3117 if 0 not in [curr_width, curr_height]:
3118 dbcfg = gmCfg.cCfgSQL()
3119 try:
3120 dbcfg.set (
3121 option = 'main.window.width',
3122 value = curr_width,
3123 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
3124 )
3125 dbcfg.set (
3126 option = 'main.window.height',
3127 value = curr_height,
3128 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
3129 )
3130 except:
3131 _log.exception('cannot save current client window size')
3132
3133 if _cfg.get(option = 'debug'):
3134 print('---=== GNUmed shutdown ===---')
3135 try:
3136 print(_('You have to manually close this window to finalize shutting down GNUmed.'))
3137 print(_('This is so that you can inspect the console output at your leisure.'))
3138 except UnicodeEncodeError:
3139 print('You have to manually close this window to finalize shutting down GNUmed.')
3140 print('This is so that you can inspect the console output at your leisure.')
3141 print('---=== GNUmed shutdown ===---')
3142
3143
3144 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
3145
3146
3147 import threading
3148 _log.debug("%s active threads", threading.activeCount())
3149 for t in threading.enumerate():
3150 _log.debug('thread %s', t)
3151 if t.name == 'MainThread':
3152 continue
3153 print('GNUmed: waiting for thread [%s] to finish' % t.name)
3154
3155 _log.debug('gmTopLevelFrame._clean_exit() end')
3156
3157
3158
3159
3161
3162 if _cfg.get(option = 'slave'):
3163 self.__title_template = 'GMdS: %%(pat)s [%%(prov)s@%%(wp)s in %%(site)s of %%(prax)s] (%s:%s)' % (
3164 _cfg.get(option = 'slave personality'),
3165 _cfg.get(option = 'xml-rpc port')
3166 )
3167 else:
3168 self.__title_template = 'GMd: %(pat)s [%(prov)s@%(wp)s in %(site)s of %(prax)s]'
3169
3171 """Update title of main window based on template.
3172
3173 This gives nice tooltips on iconified GNUmed instances.
3174
3175 User research indicates that in the title bar people want
3176 the date of birth, not the age, so please stick to this
3177 convention.
3178 """
3179 args = {}
3180
3181 pat = gmPerson.gmCurrentPatient()
3182 if pat.connected:
3183 args['pat'] = '%s %s %s (%s) #%d' % (
3184 gmTools.coalesce(pat['title'], '', '%.4s'),
3185 pat['firstnames'],
3186 pat['lastnames'],
3187 pat.get_formatted_dob(format = '%Y %b %d'),
3188 pat['pk_identity']
3189 )
3190 else:
3191 args['pat'] = _('no patient')
3192
3193 args['prov'] = '%s%s.%s' % (
3194 gmTools.coalesce(_provider['title'], '', '%s '),
3195 _provider['firstnames'][:1],
3196 _provider['lastnames']
3197 )
3198
3199 praxis = gmPraxis.gmCurrentPraxisBranch()
3200 args['wp'] = praxis.active_workplace
3201 args['site'] = praxis['branch']
3202 args['prax'] = praxis['praxis']
3203
3204 self.SetTitle(self.__title_template % args)
3205
3206
3208
3209 time.sleep(0.5)
3210
3211 rect = self.GetRect()
3212
3213
3214 if sys.platform == 'linux2':
3215 client_x, client_y = self.ClientToScreen((0, 0))
3216 border_width = client_x - rect.x
3217 title_bar_height = client_y - rect.y
3218
3219 if self.GetMenuBar():
3220 title_bar_height /= 2
3221 rect.width += (border_width * 2)
3222 rect.height += title_bar_height + border_width
3223
3224 scr_dc = wx.ScreenDC()
3225 mem_dc = wx.MemoryDC()
3226 img = wx.Bitmap(rect.width, rect.height)
3227 mem_dc.SelectObject(img)
3228 mem_dc.Blit (
3229 0, 0,
3230 rect.width, rect.height,
3231 scr_dc,
3232 rect.x, rect.y
3233 )
3234
3235
3236 if filename is None:
3237 filename = gmTools.get_unique_filename (
3238 prefix = 'gm-screenshot-%s-' % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
3239 suffix = '.png'
3240 )
3241
3242 img.SaveFile(filename, wx.BITMAP_TYPE_PNG)
3243 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % filename)
3244
3245 return filename
3246
3247
3249 sb = self.CreateStatusBar(2, wx.STB_SIZEGRIP | wx.STB_SHOW_TIPS)
3250 sb.SetStatusWidths([-1, 225])
3251
3252 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
3253 self._cb_update_clock()
3254
3255 self.clock_update_timer.Start(milliseconds = 1000)
3256
3257
3259 """Displays date and local time in the second slot of the status bar"""
3260 t = time.localtime(time.time())
3261 st = time.strftime('%Y %b %d %H:%M:%S', t)
3262 self.SetStatusText(st, 1)
3263
3264
3266 """Lock GNUmed client against unauthorized access"""
3267
3268
3269
3270 return
3271
3272
3274 """Unlock the main notebook widgets
3275 As long as we are not logged into the database backend,
3276 all pages but the 'login' page of the main notebook widget
3277 are locked; i.e. not accessible by the user
3278 """
3279
3280
3281
3282
3283
3284 return
3285
3287 wx.LayoutAlgorithm().LayoutWindow(self.LayoutMgr, self.nb)
3288
3289
3290 -class gmApp(wx.App):
3291
3293
3294 if _cfg.get(option = 'debug'):
3295 self.SetAssertMode(wx.APP_ASSERT_EXCEPTION | wx.APP_ASSERT_LOG)
3296 else:
3297 self.SetAssertMode(wx.APP_ASSERT_SUPPRESS)
3298
3299 self.__starting_up = True
3300
3301 gmExceptionHandlingWidgets.install_wx_exception_handler()
3302 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
3303
3304 self.SetAppName('gnumed')
3305 self.SetVendorName('gnumed_community')
3306 try:
3307 self.SetAppDisplayName('GNUmed %s' % _cfg.get(option = 'client_version'))
3308 except AttributeError:
3309 _log.info('SetAppDisplayName() not supported')
3310 try:
3311 self.SetVendorDisplayName('The GNUmed Development Community.')
3312 except AttributeError:
3313 _log.info('SetVendorDisplayName() not supported')
3314 paths = gmTools.gmPaths(app_name = 'gnumed', wx = wx)
3315 paths.init_paths(wx = wx, app_name = 'gnumed')
3316
3317
3318
3319
3320
3321 if sys.hexversion < 0x02070000:
3322 _log.debug('Python version < 2.7')
3323 gmGuiHelpers.gm_show_warning (
3324 aTitle = _('Python version check'),
3325 aMessage = _(
3326 'You are running Python version\n'
3327 ' %s\n'
3328 '\n'
3329 'However, GNUmed wants Python 2.7 to\n'
3330 'facilitate migration to Python 3.\n'
3331 '\n'
3332 'Please upgrade your Python interpreter !'
3333 ) % sys.version
3334 )
3335
3336 if not self.__setup_prefs_file():
3337 return False
3338
3339 gmExceptionHandlingWidgets.set_sender_email(gmPraxis.gmCurrentPraxisBranch().user_email)
3340
3341 self.__guibroker = gmGuiBroker.GuiBroker()
3342 self.__setup_platform()
3343
3344 if not self.__establish_backend_connection():
3345 return False
3346 if not self.__verify_db_account():
3347 return False
3348 if not self.__verify_praxis_branch():
3349 return False
3350
3351 self.__check_db_lang()
3352 self.__update_workplace_list()
3353
3354 if not _cfg.get(option = 'skip-update-check'):
3355 self.__check_for_updates()
3356
3357 if _cfg.get(option = 'slave'):
3358 if not self.__setup_scripting_listener():
3359 return False
3360
3361
3362 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
3363 frame.CentreOnScreen(wx.BOTH)
3364 self.SetTopWindow(frame)
3365 frame.Show(True)
3366
3367 if _cfg.get(option = 'debug'):
3368 self.RedirectStdio()
3369 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
3370
3371
3372 print('---=== GNUmed startup ===---')
3373 print(_('redirecting STDOUT/STDERR to this log window'))
3374 print('---=== GNUmed startup ===---')
3375
3376 self.__setup_user_activity_timer()
3377 self.__register_events()
3378
3379 wx.CallAfter(self._do_after_init)
3380
3381 return True
3382
3384 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
3385
3386 - after destroying all application windows and controls
3387 - before wx.Windows internal cleanup
3388 """
3389 _log.debug('gmApp.OnExit() start')
3390
3391 self.__shutdown_user_activity_timer()
3392
3393 if _cfg.get(option = 'debug'):
3394 self.RestoreStdio()
3395 sys.stdin = sys.__stdin__
3396 sys.stdout = sys.__stdout__
3397 sys.stderr = sys.__stderr__
3398
3399 top_wins = wx.GetTopLevelWindows()
3400 if len(top_wins) > 0:
3401 _log.debug('%s top level windows still around in <app>.OnExit()', len(top_wins))
3402 _log.debug(top_wins)
3403 for win in top_wins:
3404 _log.debug('destroying: %s', win)
3405 win.Destroy()
3406
3407 _log.debug('gmApp.OnExit() end')
3408 return 0
3409
3410
3412 wx.Bell()
3413 wx.Bell()
3414 wx.Bell()
3415 _log.warning('unhandled event detected: QUERY_END_SESSION')
3416 _log.info('we should be saving ourselves from here')
3417 gmLog2.flush()
3418 print('unhandled event detected: QUERY_END_SESSION')
3419
3421 wx.Bell()
3422 wx.Bell()
3423 wx.Bell()
3424 _log.warning('unhandled event detected: END_SESSION')
3425 gmLog2.flush()
3426 print('unhandled event detected: END_SESSION')
3427
3438
3440 self.user_activity_detected = True
3441 evt.Skip()
3442
3444
3445 if self.user_activity_detected:
3446 self.elapsed_inactivity_slices = 0
3447 self.user_activity_detected = False
3448 self.elapsed_inactivity_slices += 1
3449 else:
3450 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
3451
3452 pass
3453
3454 self.user_activity_timer.Start(oneShot = True)
3455
3456
3457
3459 self.__starting_up = False
3460
3461 self.__guibroker['horstspace.top_panel']._TCTRL_patient_selector.SetFocus()
3462 gmHooks.run_hook_script(hook = 'startup-after-GUI-init')
3463
3464
3466 self.user_activity_detected = True
3467 self.elapsed_inactivity_slices = 0
3468
3469 self.max_user_inactivity_slices = 15
3470 self.user_activity_timer = gmTimer.cTimer (
3471 callback = self._on_user_activity_timer_expired,
3472 delay = 2000
3473 )
3474 self.user_activity_timer.Start(oneShot=True)
3475
3476
3478 try:
3479 self.user_activity_timer.Stop()
3480 del self.user_activity_timer
3481 except:
3482 pass
3483
3484
3486 self.Bind(wx.EVT_QUERY_END_SESSION, self._on_query_end_session)
3487 self.Bind(wx.EVT_END_SESSION, self._on_end_session)
3488
3489
3490
3491
3492
3493 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
3494
3495 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
3496 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
3497
3498
3512
3513
3526
3555
3557
3558 if not gmPraxisWidgets.set_active_praxis_branch(no_parent = True):
3559 return False
3560
3561 login = gmPG2.get_default_login()
3562 msg = '\n'
3563 msg += _('Database <%s> on <%s>') % (
3564 login.database,
3565 gmTools.coalesce(login.host, 'localhost')
3566 )
3567 msg += '\n\n'
3568
3569 praxis = gmPraxis.gmCurrentPraxisBranch()
3570 msg += _('Branch "%s" of praxis "%s"\n') % (
3571 praxis['branch'],
3572 praxis['praxis']
3573 )
3574 msg += '\n\n'
3575
3576 banner = praxis.db_logon_banner
3577 if banner.strip() == '':
3578 return True
3579 msg += banner
3580 msg += '\n\n'
3581
3582 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3583 None,
3584 -1,
3585 caption = _('Verifying database'),
3586 question = gmTools.wrap(msg, 60, initial_indent = ' ', subsequent_indent = ' '),
3587 button_defs = [
3588 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3589 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3590 ]
3591 )
3592 log_on = dlg.ShowModal()
3593 dlg.Destroy()
3594 if log_on == wx.ID_YES:
3595 return True
3596 _log.info('user decided to not connect to this database')
3597 return False
3598
3612
3614 """Setup access to a config file for storing preferences."""
3615
3616 paths = gmTools.gmPaths(app_name = 'gnumed', wx = wx)
3617
3618 candidates = []
3619 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3620 if explicit_file is not None:
3621 candidates.append(explicit_file)
3622
3623 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3624 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3625 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3626
3627 prefs_file = None
3628 for candidate in candidates:
3629 try:
3630 open(candidate, 'a+').close()
3631 prefs_file = candidate
3632 break
3633 except IOError:
3634 continue
3635
3636 if prefs_file is None:
3637 msg = _(
3638 'Cannot find configuration file in any of:\n'
3639 '\n'
3640 ' %s\n'
3641 'You may need to use the comand line option\n'
3642 '\n'
3643 ' --conf-file=<FILE>'
3644 ) % '\n '.join(candidates)
3645 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3646 return False
3647
3648 _cfg.set_option(option = 'user_preferences_file', value = prefs_file)
3649 _log.info('user preferences file: %s', prefs_file)
3650
3651 return True
3652
3654
3655 from socket import error as SocketError
3656 from Gnumed.pycommon import gmScriptingListener
3657 from Gnumed.wxpython import gmMacro
3658
3659 slave_personality = gmTools.coalesce (
3660 _cfg.get (
3661 group = 'workplace',
3662 option = 'slave personality',
3663 source_order = [
3664 ('explicit', 'return'),
3665 ('workbase', 'return'),
3666 ('user', 'return'),
3667 ('system', 'return')
3668 ]
3669 ),
3670 'gnumed-client'
3671 )
3672 _cfg.set_option(option = 'slave personality', value = slave_personality)
3673
3674
3675 port = int (
3676 gmTools.coalesce (
3677 _cfg.get (
3678 group = 'workplace',
3679 option = 'xml-rpc port',
3680 source_order = [
3681 ('explicit', 'return'),
3682 ('workbase', 'return'),
3683 ('user', 'return'),
3684 ('system', 'return')
3685 ]
3686 ),
3687 9999
3688 )
3689 )
3690 _cfg.set_option(option = 'xml-rpc port', value = port)
3691
3692 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3693 global _scripting_listener
3694 try:
3695 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3696 except SocketError as e:
3697 _log.exception('cannot start GNUmed XML-RPC server')
3698 gmGuiHelpers.gm_show_error (
3699 aMessage = (
3700 'Cannot start the GNUmed server:\n'
3701 '\n'
3702 ' [%s]'
3703 ) % e,
3704 aTitle = _('GNUmed startup')
3705 )
3706 return False
3707
3708 return True
3709
3730
3732 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3733 _log.warning("system locale is undefined (probably meaning 'C')")
3734 return True
3735
3736 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': "select i18n.get_curr_lang() as lang"}])
3737 curr_db_lang = rows[0]['lang']
3738 _log.debug("current database locale: [%s]" % curr_db_lang)
3739
3740 if curr_db_lang is None:
3741
3742 cmd = 'select i18n.set_curr_lang(%s)'
3743 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3744 if len(lang) == 0:
3745 continue
3746 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
3747 if rows[0][0]:
3748 _log.debug("Successfully set database language to [%s]." % lang)
3749 return True
3750 _log.error('Cannot set database language to [%s].' % lang)
3751
3752 return True
3753
3754 if curr_db_lang == gmI18N.system_locale_level['full']:
3755 _log.debug('Database locale (%s) up to date.' % curr_db_lang)
3756 return True
3757 if curr_db_lang == gmI18N.system_locale_level['country']:
3758 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (curr_db_lang, gmI18N.system_locale))
3759 return True
3760 if curr_db_lang == gmI18N.system_locale_level['language']:
3761 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (curr_db_lang, gmI18N.system_locale))
3762 return True
3763
3764 _log.warning('database locale [%s] does not match system locale [%s]' % (curr_db_lang, gmI18N.system_locale))
3765
3766 sys_lang2ignore = _cfg.get (
3767 group = 'backend',
3768 option = 'ignored mismatching system locale',
3769 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3770 )
3771 if gmI18N.system_locale == sys_lang2ignore:
3772 _log.info('configured to ignore system-to-database locale mismatch')
3773 return True
3774
3775
3776 msg = _(
3777 "The currently selected database language ('%s') does\n"
3778 "not match the current system language ('%s').\n"
3779 "\n"
3780 "Do you want to set the database language to '%s' ?\n"
3781 ) % (curr_db_lang, gmI18N.system_locale, gmI18N.system_locale)
3782 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3783 None,
3784 -1,
3785 caption = _('Checking database language settings'),
3786 question = msg,
3787 button_defs = [
3788 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3789 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3790 ],
3791 show_checkbox = True,
3792 checkbox_msg = _('Remember to ignore language mismatch'),
3793 checkbox_tooltip = _(
3794 'Checking this will make GNUmed remember your decision\n'
3795 'until the system language is changed.\n'
3796 '\n'
3797 'You can also reactivate this inquiry by removing the\n'
3798 'corresponding "ignore" option from the configuration file\n'
3799 '\n'
3800 ' [%s]'
3801 ) % _cfg.get(option = 'user_preferences_file')
3802 )
3803 decision = dlg.ShowModal()
3804 remember2ignore_this_mismatch = dlg._CHBOX_dont_ask_again.GetValue()
3805 dlg.Destroy()
3806
3807 if decision == wx.ID_NO:
3808 if not remember2ignore_this_mismatch:
3809 return True
3810 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3811 gmCfg2.set_option_in_INI_file (
3812 filename = _cfg.get(option = 'user_preferences_file'),
3813 group = 'backend',
3814 option = 'ignored mismatching system locale',
3815 value = gmI18N.system_locale
3816 )
3817 return True
3818
3819
3820 cmd = 'select i18n.set_curr_lang(%s)'
3821 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3822 if len(lang) == 0:
3823 continue
3824 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
3825 if rows[0][0]:
3826 _log.debug("Successfully set database language to [%s]." % lang)
3827 return True
3828 _log.error('Cannot set database language to [%s].' % lang)
3829
3830
3831 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3832 gmPG2.run_rw_queries(queries = [{
3833 'cmd': 'select i18n.force_curr_lang(%s)',
3834 'args': [gmI18N.system_locale_level['country']]
3835 }])
3836
3837 return True
3838
3841 try:
3842 kwargs['originated_in_database']
3843 print('==> got notification from database "%s":' % kwargs['signal'])
3844 except KeyError:
3845 print('==> received signal from client: "%s"' % kwargs['signal'])
3846
3847 del kwargs['signal']
3848 for key in kwargs:
3849
3850 try: print(' [%s]: %s' % (key, kwargs[key]))
3851 except: print('cannot print signal information')
3852
3857
3869
3874
3877
3878
3879
3880 gmDispatcher.set_main_thread_caller(wx.CallAfter)
3881
3882 if _cfg.get(option = 'debug'):
3883 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3884 _log.debug('gmDispatcher signal monitor activated')
3885
3886 setup_safe_wxEndBusyCursor()
3887
3888 setup_callbacks()
3889
3890
3891
3892
3893 app = gmApp(redirect = False, clearSigInt = False)
3894 app.MainLoop()
3895
3896
3897
3898
3899 if __name__ == '__main__':
3900
3901 from GNUmed.pycommon import gmI18N
3902 gmI18N.activate_locale()
3903 gmI18N.install_domain()
3904
3905 _log.info('Starting up as main module.')
3906 main()
3907
3908
3909