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