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_statusbar()
170 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
171 gmTools.coalesce(_provider['title'], ''),
172 _provider['firstnames'][:1],
173 _provider['lastnames'],
174 _provider['short_alias'],
175 _provider['db_user']
176 ))
177 self.__setup_main_menu()
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, _('Browse work dir'), _('Browse user working directory [%s].') % os.path.join(gmTools.gmPaths().home_dir, 'gnumed'))
754 self.Bind(wx.EVT_MENU, self.__on_browse_work_dir, item)
755
756 menu_debugging = wx.Menu()
757 item = menu_debugging.Append(-1, _('Status line: &Clear'), _('Clear the status line.'))
758 self.Bind(wx.EVT_MENU, self.__on_clear_status_line, item)
759 item = menu_debugging.Append(-1, _('Status line: History'), _('Show status line history.'))
760 self.Bind(wx.EVT_MENU, self.__on_show_status_line_history, item)
761 item = menu_debugging.Append(-1, _('Tooltips on'), _('Globally enable tooltips.'))
762 self.Bind(wx.EVT_MENU, self.__on_enable_tooltips, item)
763 item = menu_debugging.Append(-1, _('Tooltips off'), _('Globally (attempt to) disable tooltips.'))
764 self.Bind(wx.EVT_MENU, self.__on_disable_tooltips, item)
765 item = menu_debugging.Append(-1, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
766 self.Bind(wx.EVT_MENU, self.__on_save_screenshot, item)
767 item = menu_debugging.Append(-1, _('Show log file'), _('Show log file in text viewer.'))
768 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
769 item = menu_debugging.Append(-1, _('Backup log file'), _('Backup content of the log to another file.'))
770 self.Bind(wx.EVT_MENU, self.__on_backup_log_file, item)
771 item = menu_debugging.Append(-1, _('Email log file'), _('Send log file to the authors for help.'))
772 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
773 item = menu_debugging.Append(-1, _('Browse tmp dir'), _('Browse temporary directory [%s].') % gmTools.gmPaths().tmp_dir)
774 self.Bind(wx.EVT_MENU, self.__on_browse_tmp_dir, item)
775 item = menu_debugging.Append(-1, _('Browse internal work dir'), _('Browse internal working directory [%s].') % os.path.join(gmTools.gmPaths().home_dir, '.gnumed'))
776 self.Bind(wx.EVT_MENU, self.__on_browse_internal_work_dir, item)
777 item = menu_debugging.Append(-1, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
778 self.Bind(wx.EVT_MENU, self.__on_display_bugtracker, item)
779 item = menu_debugging.Append(-1, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
780 self.Bind(wx.EVT_MENU, self.__on_unblock_cursor, item)
781 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
782 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
783
784
785 if _cfg.get(option = 'debug'):
786 item = menu_debugging.Append(-1, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
787 self.Bind(wx.EVT_MENU, self.__on_toggle_patient_lock, item)
788 item = menu_debugging.Append(-1, _('Test error handling'), _('Throw an exception to test error handling.'))
789 self.Bind(wx.EVT_MENU, self.__on_test_exception, item)
790 item = menu_debugging.Append(-1, _('Test access violation exception'), _('Simulate an access violation exception.'))
791 self.Bind(wx.EVT_MENU, self.__on_test_access_violation, item)
792 item = menu_debugging.Append(-1, _('Test access checking'), _('Simulate a failing access check.'))
793 self.Bind(wx.EVT_MENU, self.__on_test_access_checking, item)
794 item = menu_debugging.Append(-1, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
795 self.Bind(wx.EVT_MENU, self.__on_invoke_inspector, item)
796 try:
797 import wx.lib.inspection
798 except ImportError:
799 menu_debugging.Enable(id = ID, enable = False)
800 try:
801 import faulthandler
802 item = menu_debugging.Append(-1, _('Test fault handler'), _('Simulate a catastrophic fault (SIGSEGV).'))
803 self.Bind(wx.EVT_MENU, self.__on_test_segfault, item)
804 except ImportError:
805 pass
806 item = menu_debugging.Append(-1, _('Test placeholder'), _('Manually test placeholders'))
807 self.Bind(wx.EVT_MENU, self.__on_test_placeholders, item)
808
809 help_menu.Append(wx.NewId(), _('Debugging ...'), menu_debugging)
810 help_menu.AppendSeparator()
811
812 item = help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), '')
813 self.Bind(wx.EVT_MENU, self.OnAbout, item)
814 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
815 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
816 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors'))
817 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item)
818 help_menu.AppendSeparator()
819
820 self.mainmenu.Append(help_menu, _("&Help"))
821
822 self.__gb['main.helpmenu'] = help_menu
823
824
825 self.SetMenuBar(self.mainmenu)
826
827
830
831
832
834 """register events we want to react to"""
835
836 self.Bind(wx.EVT_CLOSE, self.OnClose)
837 self.Bind(wx.EVT_QUERY_END_SESSION, self._on_query_end_session)
838 self.Bind(wx.EVT_END_SESSION, self._on_end_session)
839
840 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
841 gmDispatcher.connect(signal = 'statustext', receiver = self._on_set_statustext)
842 gmDispatcher.connect(signal = 'request_user_attention', receiver = self._on_request_user_attention)
843 gmDispatcher.connect(signal = 'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
844 gmDispatcher.connect(signal = 'plugin_loaded', receiver = self._on_plugin_loaded)
845
846 gmDispatcher.connect(signal = 'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
847 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
848
849
850
851 gmPerson.gmCurrentPatient().register_before_switching_from_patient_callback(callback = self._before_switching_from_patient_callback)
852
853
855
856 if kwds['table'] == 'dem.praxis_branch':
857 if kwds['operation'] != 'UPDATE':
858 return True
859 branch = gmPraxis.gmCurrentPraxisBranch()
860 if branch['pk_praxis_branch'] != kwds['pk_of_row']:
861 return True
862 self.__update_window_title()
863 return True
864
865 if kwds['table'] == 'dem.names':
866 pat = gmPerson.gmCurrentPatient()
867 if pat.connected:
868 if pat.ID != kwds['pk_identity']:
869 return True
870 self.__update_window_title()
871 return True
872
873 if kwds['table'] == 'dem.identity':
874 if kwds['operation'] != 'UPDATE':
875 return True
876 pat = gmPerson.gmCurrentPatient()
877 if pat.connected:
878 if pat.ID != kwds['pk_identity']:
879 return True
880 self.__update_window_title()
881 return True
882
883 return True
884
885
886 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
887
888 _log.debug('registering plugin with menu system')
889 _log.debug(' generic name: %s', plugin_name)
890 _log.debug(' class name: %s', class_name)
891 _log.debug(' specific menu: %s', menu_name)
892 _log.debug(' menu item: %s', menu_item_name)
893
894
895 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
896 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
897 self.menu_id2plugin[item.Id] = class_name
898
899
900 if menu_name is not None:
901 menu = self.__gb['main.%smenu' % menu_name]
902 item = menu.Append(-1, menu_item_name, menu_help_string)
903 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
904 self.menu_id2plugin[item.Id] = class_name
905
906 return True
907
913
915 wx.Bell()
916 wx.Bell()
917 wx.Bell()
918 _log.warning('unhandled event detected: QUERY_END_SESSION')
919 _log.info('we should be saving ourselves from here')
920 gmLog2.flush()
921 print('unhandled event detected: QUERY_END_SESSION')
922
924 wx.Bell()
925 wx.Bell()
926 wx.Bell()
927 _log.warning('unhandled event detected: END_SESSION')
928 gmLog2.flush()
929 print('unhandled event detected: END_SESSION')
930
931
933 if not callable(callback):
934 raise TypeError('callback [%s] not callable' % callback)
935
936 self.__pre_exit_callbacks.append(callback)
937
938
939 - def _on_set_statustext_pubsub(self, context=None):
940 try:
941 beep = context.data['beep']
942 except KeyError:
943 beep = False
944 wx.CallAfter(self.SetStatusText, '%s' % context.data['msg'], beep = beep)
945
946
947 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
948 if msg is None:
949 msg = _('programmer forgot to specify status message')
950 if loglevel is not None:
951 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
952 wx.CallAfter(self.SetStatusText, msg, beep = beep)
953
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
994
996
997 if not wx.GetApp().IsActive():
998 if urgent:
999 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
1000 else:
1001 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
1002
1003 if msg is not None:
1004 self.SetStatusText(msg)
1005
1006 if urgent:
1007 wx.Bell()
1008
1009 gmHooks.run_hook_script(hook = 'request_user_attention')
1010
1011 - def _on_post_patient_selection(self, **kwargs):
1012 self.__update_window_title()
1013 gmDispatcher.send(signal = 'statustext', msg = '')
1014 try:
1015 gmHooks.run_hook_script(hook = 'post_patient_activation')
1016 except:
1017 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
1018 raise
1019
1027
1028
1029
1032
1039
1040
1044
1045
1047 evt.Skip()
1048 pat = gmPerson.gmCurrentPatient()
1049 if not pat.connected:
1050 gmDispatcher.send(signal = 'statustext', msg = _('Cannot put screenshot into export area. No active patient.'), beep = True)
1051 return False
1052
1053 screenshot_file = self.__save_screenshot_to_file()
1054 if not os.path.exists(screenshot_file):
1055 gmDispatcher.send(signal = 'statustext', msg = _('Cannot put screenshot into export area. No screenshot found.'), beep = True)
1056 return False
1057
1058 pat.export_area.add_file(filename = screenshot_file, hint = _('GMd screenshot'))
1059 return True
1060
1061
1075
1076
1077
1078
1080
1081 return
1082
1083
1084 from Gnumed.wxpython import gmAbout
1085 frame_about = gmAbout.AboutFrame (
1086 self,
1087 -1,
1088 _("About GNUmed"),
1089 size=wx.Size(350, 300),
1090 style = wx.MAXIMIZE_BOX,
1091 version = _cfg.get(option = 'client_version'),
1092 debug = _cfg.get(option = 'debug')
1093 )
1094 frame_about.Centre(wx.BOTH)
1095 gmTopLevelFrame.otherWin = frame_about
1096 frame_about.Show(True)
1097 frame_about.DestroyLater()
1098
1099
1130
1131
1133 from Gnumed.wxpython import gmAbout
1134 contribs = gmAbout.cContributorsDlg (
1135 parent = self,
1136 id = -1,
1137 title = _('GNUmed contributors'),
1138 size = wx.Size(400,600),
1139 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1140 )
1141 contribs.ShowModal()
1142 contribs.DestroyLater()
1143
1144
1145
1146
1148 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1149 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1150 self.Close(True)
1151 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1152
1153
1156
1157
1159 send = gmGuiHelpers.gm_show_question (
1160 _('This will send a notification about database downtime\n'
1161 'to all GNUmed clients connected to your database.\n'
1162 '\n'
1163 'Do you want to send the notification ?\n'
1164 ),
1165 _('Announcing database maintenance downtime')
1166 )
1167 if not send:
1168 return
1169 gmPG2.send_maintenance_notification()
1170
1171
1174
1175
1176
1189
1190 gmCfgWidgets.configure_string_option (
1191 message = _(
1192 'Some network installations cannot cope with loading\n'
1193 'documents of arbitrary size in one piece from the\n'
1194 'database (mainly observed on older Windows versions)\n.'
1195 '\n'
1196 'Under such circumstances documents need to be retrieved\n'
1197 'in chunks and reassembled on the client.\n'
1198 '\n'
1199 'Here you can set the size (in Bytes) above which\n'
1200 'GNUmed will retrieve documents in chunks. Setting this\n'
1201 'value to 0 will disable the chunking protocol.'
1202 ),
1203 option = 'horstspace.blob_export_chunk_size',
1204 bias = 'workplace',
1205 default_value = 1024 * 1024,
1206 validator = is_valid
1207 )
1208
1209
1210
1278
1282
1283
1284
1293
1294 gmCfgWidgets.configure_string_option (
1295 message = _(
1296 'When GNUmed cannot find an OpenOffice server it\n'
1297 'will try to start one. OpenOffice, however, needs\n'
1298 'some time to fully start up.\n'
1299 '\n'
1300 'Here you can set the time for GNUmed to wait for OOo.\n'
1301 ),
1302 option = 'external.ooo.startup_settle_time',
1303 bias = 'workplace',
1304 default_value = 2.0,
1305 validator = is_valid
1306 )
1307
1310
1311
1314
1315
1318
1319
1322
1323
1338
1339 gmCfgWidgets.configure_string_option (
1340 message = _(
1341 'GNUmed will use this URL to access an encyclopedia of\n'
1342 'measurement/lab methods from within the measurments grid.\n'
1343 '\n'
1344 'You can leave this empty but to set it to a specific\n'
1345 'address the URL must be accessible now.'
1346 ),
1347 option = 'external.urls.measurements_encyclopedia',
1348 bias = 'user',
1349 default_value = german_default,
1350 validator = is_valid
1351 )
1352
1353
1366
1367 gmCfgWidgets.configure_string_option (
1368 message = _(
1369 'Enter the shell command with which to start the\n'
1370 'the ACS risk assessment calculator.\n'
1371 '\n'
1372 'GNUmed will try to verify the path which may,\n'
1373 'however, fail if you are using an emulator such\n'
1374 'as Wine. Nevertheless, starting the calculator\n'
1375 'will work as long as the shell command is correct\n'
1376 'despite the failing test.'
1377 ),
1378 option = 'external.tools.acs_risk_calculator_cmd',
1379 bias = 'user',
1380 validator = is_valid
1381 )
1382
1385
1398
1399 gmCfgWidgets.configure_string_option (
1400 message = _(
1401 'Enter the shell command with which to start\n'
1402 'the FreeDiams drug database frontend.\n'
1403 '\n'
1404 'GNUmed will try to verify that path.'
1405 ),
1406 option = 'external.tools.freediams_cmd',
1407 bias = 'workplace',
1408 default_value = None,
1409 validator = is_valid
1410 )
1411
1424
1425 gmCfgWidgets.configure_string_option (
1426 message = _(
1427 'Enter the shell command with which to start the\n'
1428 'the IFAP drug database.\n'
1429 '\n'
1430 'GNUmed will try to verify the path which may,\n'
1431 'however, fail if you are using an emulator such\n'
1432 'as Wine. Nevertheless, starting IFAP will work\n'
1433 'as long as the shell command is correct despite\n'
1434 'the failing test.'
1435 ),
1436 option = 'external.ifap-win.shell_command',
1437 bias = 'workplace',
1438 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1439 validator = is_valid
1440 )
1441
1442
1443
1492
1493
1494
1511
1514
1517
1522
1523 gmCfgWidgets.configure_string_option (
1524 message = _(
1525 'When a patient is activated GNUmed checks the\n'
1526 "proximity of the patient's birthday.\n"
1527 '\n'
1528 'If the birthday falls within the range of\n'
1529 ' "today %s <the interval you set here>"\n'
1530 'GNUmed will remind you of the recent or\n'
1531 'imminent anniversary.'
1532 ) % '\u2213',
1533 option = 'patient_search.dob_warn_interval',
1534 bias = 'user',
1535 default_value = '1 week',
1536 validator = is_valid
1537 )
1538
1540
1541 gmCfgWidgets.configure_boolean_option (
1542 parent = self,
1543 question = _(
1544 'When adding progress notes do you want to\n'
1545 'allow opening several unassociated, new\n'
1546 'episodes for a patient at once ?\n'
1547 '\n'
1548 'This can be particularly helpful when entering\n'
1549 'progress notes on entirely new patients presenting\n'
1550 'with a multitude of problems on their first visit.'
1551 ),
1552 option = 'horstspace.soap_editor.allow_same_episode_multiple_times',
1553 button_tooltips = [
1554 _('Yes, allow for multiple new episodes concurrently.'),
1555 _('No, only allow editing one new episode at a time.')
1556 ]
1557 )
1558
1560
1561 gmCfgWidgets.configure_boolean_option (
1562 parent = self,
1563 question = _(
1564 'When activating a patient, do you want GNUmed to\n'
1565 'auto-open editors for all active problems that were\n'
1566 'touched upon during the current and the most recent\n'
1567 'encounter ?'
1568 ),
1569 option = 'horstspace.soap_editor.auto_open_latest_episodes',
1570 button_tooltips = [
1571 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1572 _('No, only auto-open one editor for a new, unassociated problem.')
1573 ]
1574 )
1575
1576
1578 gmCfgWidgets.configure_boolean_option (
1579 parent = self,
1580 question = _(
1581 'When editing progress notes, do you want GNUmed to\n'
1582 'show individual fields for each of the SOAP categories\n'
1583 'or do you want to use a text-editor like field for\n'
1584 'all SOAP categories which can then be set per line\n'
1585 'of input ?'
1586 ),
1587 option = 'horstspace.soap_editor.use_one_field_per_soap_category',
1588 button_tooltips = [
1589 _('Yes, show a dedicated field per SOAP category.'),
1590 _('No, use one field for all SOAP categories.')
1591 ]
1592 )
1593
1594
1640
1641
1642
1645
1648
1661
1662 gmCfgWidgets.configure_string_option (
1663 message = _(
1664 'GNUmed will use this URL to let you browse\n'
1665 'billing catalogs (schedules of fees).\n'
1666 '\n'
1667 'You can leave this empty but to set it to a specific\n'
1668 'address the URL must be accessible now.'
1669 ),
1670 option = 'external.urls.schedules_of_fees',
1671 bias = 'user',
1672 default_value = german_default,
1673 validator = is_valid
1674 )
1675
1676
1677
1680
1683
1685 gmCfgWidgets.configure_string_from_list_option (
1686 parent = self,
1687 message = _('Select the default prescription mode.\n'),
1688 option = 'horst_space.default_prescription_mode',
1689 bias = 'user',
1690 default_value = 'form',
1691 choices = [ _('Formular'), _('Datenbank') ],
1692 columns = [_('Prescription mode')],
1693 data = [ 'form', 'database' ]
1694 )
1695
1698
1701
1704
1707
1709 enc_types = gmEMRStructItems.get_encounter_types()
1710 msg = _(
1711 'Select the default type for new encounters.\n'
1712 '\n'
1713 'Leaving this unset will make GNUmed apply the most commonly used type.\n'
1714 )
1715 gmCfgWidgets.configure_string_from_list_option (
1716 parent = self,
1717 message = msg,
1718 option = 'encounter.default_type',
1719 bias = 'user',
1720
1721 choices = [ e[0] for e in enc_types ],
1722 columns = [_('Encounter type')],
1723 data = [ e[1] for e in enc_types ]
1724 )
1725
1727 gmCfgWidgets.configure_boolean_option (
1728 parent = self,
1729 question = _(
1730 'Do you want GNUmed to show the encounter\n'
1731 'details editor when changing the active patient ?'
1732 ),
1733 option = 'encounter.show_editor_before_patient_change',
1734 button_tooltips = [
1735 _('Yes, show the encounter editor if it seems appropriate.'),
1736 _('No, never show the encounter editor even if it would seem useful.')
1737 ]
1738 )
1739
1744
1745 gmCfgWidgets.configure_string_option (
1746 message = _(
1747 'When a patient is activated GNUmed checks the\n'
1748 'chart for encounters lacking any entries.\n'
1749 '\n'
1750 'Any such encounters older than what you set\n'
1751 'here will be removed from the medical record.\n'
1752 '\n'
1753 'To effectively disable removal of such encounters\n'
1754 'set this option to an improbable value.\n'
1755 ),
1756 option = 'encounter.ttl_if_empty',
1757 bias = 'user',
1758 default_value = '1 week',
1759 validator = is_valid
1760 )
1761
1766
1767 gmCfgWidgets.configure_string_option (
1768 message = _(
1769 'When a patient is activated GNUmed checks the\n'
1770 'age of the most recent encounter.\n'
1771 '\n'
1772 'If that encounter is younger than this age\n'
1773 'the existing encounter will be continued.\n'
1774 '\n'
1775 '(If it is really old a new encounter is\n'
1776 ' started, or else GNUmed will ask you.)\n'
1777 ),
1778 option = 'encounter.minimum_ttl',
1779 bias = 'user',
1780 default_value = '1 hour 30 minutes',
1781 validator = is_valid
1782 )
1783
1788
1789 gmCfgWidgets.configure_string_option (
1790 message = _(
1791 'When a patient is activated GNUmed checks the\n'
1792 'age of the most recent encounter.\n'
1793 '\n'
1794 'If that encounter is older than this age\n'
1795 'GNUmed will always start a new encounter.\n'
1796 '\n'
1797 '(If it is very recent the existing encounter\n'
1798 ' is continued, or else GNUmed will ask you.)\n'
1799 ),
1800 option = 'encounter.maximum_ttl',
1801 bias = 'user',
1802 default_value = '6 hours',
1803 validator = is_valid
1804 )
1805
1814
1815 gmCfgWidgets.configure_string_option (
1816 message = _(
1817 'At any time there can only be one open (ongoing)\n'
1818 'episode for each health issue.\n'
1819 '\n'
1820 'When you try to open (add data to) an episode on a health\n'
1821 'issue GNUmed will check for an existing open episode on\n'
1822 'that issue. If there is any it will check the age of that\n'
1823 'episode. The episode is closed if it has been dormant (no\n'
1824 'data added, that is) for the period of time (in days) you\n'
1825 'set here.\n'
1826 '\n'
1827 "If the existing episode hasn't been dormant long enough\n"
1828 'GNUmed will consult you what to do.\n'
1829 '\n'
1830 'Enter maximum episode dormancy in DAYS:'
1831 ),
1832 option = 'episode.ttl',
1833 bias = 'user',
1834 default_value = 60,
1835 validator = is_valid
1836 )
1837
1868
1883
1908
1918
1919 gmCfgWidgets.configure_string_option (
1920 message = _(
1921 'GNUmed can check for new releases being available. To do\n'
1922 'so it needs to load version information from an URL.\n'
1923 '\n'
1924 'The default URL is:\n'
1925 '\n'
1926 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1927 '\n'
1928 'but you can configure any other URL locally. Note\n'
1929 'that you must enter the location as a valid URL.\n'
1930 'Depending on the URL the client will need online\n'
1931 'access when checking for updates.'
1932 ),
1933 option = 'horstspace.update.url',
1934 bias = 'workplace',
1935 default_value = 'http://www.gnumed.de/downloads/gnumed-versions.txt',
1936 validator = is_valid
1937 )
1938
1956
1973
1990
2001
2002 gmCfgWidgets.configure_string_option (
2003 message = _(
2004 'GNUmed can show the document review dialog after\n'
2005 'calling the appropriate viewer for that document.\n'
2006 '\n'
2007 'Select the conditions under which you want\n'
2008 'GNUmed to do so:\n'
2009 '\n'
2010 ' 0: never display the review dialog\n'
2011 ' 1: always display the dialog\n'
2012 ' 2: only if there is no previous review by me\n'
2013 ' 3: only if there is no previous review at all\n'
2014 ' 4: only if there is no review by the responsible reviewer\n'
2015 '\n'
2016 'Note that if a viewer is configured to not block\n'
2017 'GNUmed during document display the review dialog\n'
2018 'will actually appear in parallel to the viewer.'
2019 ),
2020 option = 'horstspace.document_viewer.review_after_display',
2021 bias = 'user',
2022 default_value = 3,
2023 validator = is_valid
2024 )
2025
2027
2028
2029 master_data_lists = [
2030 'adr',
2031 'provinces',
2032 'codes',
2033 'billables',
2034 'ref_data_sources',
2035 'meds_substances',
2036 'meds_doses',
2037 'meds_components',
2038 'meds_drugs',
2039 'meds_vaccines',
2040 'orgs',
2041 'labs',
2042 'meta_test_types',
2043 'test_types',
2044 'test_panels',
2045 'form_templates',
2046 'doc_types',
2047 'enc_types',
2048 'communication_channel_types',
2049 'text_expansions',
2050 'patient_tags',
2051 'hints',
2052 'db_translations',
2053 'workplaces'
2054 ]
2055
2056 master_data_list_names = {
2057 'adr': _('Addresses (likely slow)'),
2058 'hints': _('Dynamic automatic hints'),
2059 'codes': _('Codes and their respective terms'),
2060 'communication_channel_types': _('Communication channel types'),
2061 'orgs': _('Organizations with their units, addresses, and comm channels'),
2062 'labs': _('Measurements: diagnostic organizations (path labs, ...)'),
2063 'test_types': _('Measurements: test types'),
2064 'test_panels': _('Measurements: test panels/profiles/batteries'),
2065 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2066 'doc_types': _('Document types'),
2067 'enc_types': _('Encounter types'),
2068 'text_expansions': _('Keyword based text expansion macros'),
2069 'meta_test_types': _('Measurements: aggregate test types'),
2070 'patient_tags': _('Patient tags'),
2071 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2072 'db_translations': _('String translations in the database'),
2073 'meds_vaccines': _('Medications: vaccines'),
2074 'meds_substances': _('Medications: base substances'),
2075 'meds_doses': _('Medications: substance dosage'),
2076 'meds_components': _('Medications: drug components'),
2077 'meds_drugs': _('Medications: drug products and generic drugs'),
2078 'workplaces': _('Workplace profiles (which plugins to load)'),
2079 'billables': _('Billable items'),
2080 'ref_data_sources': _('Reference data sources')
2081 }
2082
2083 map_list2handler = {
2084 'form_templates': gmFormWidgets.manage_form_templates,
2085 'doc_types': gmDocumentWidgets.manage_document_types,
2086 'text_expansions': gmKeywordExpansionWidgets.configure_keyword_text_expansion,
2087 'db_translations': gmI18nWidgets.manage_translations,
2088 'codes': gmCodingWidgets.browse_coded_terms,
2089 'enc_types': gmEncounterWidgets.manage_encounter_types,
2090 'provinces': gmAddressWidgets.manage_regions,
2091 'workplaces': gmPraxisWidgets.configure_workplace_plugins,
2092 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2093 'test_types': gmMeasurementWidgets.manage_measurement_types,
2094 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2095 'orgs': gmOrganizationWidgets.manage_orgs,
2096 'adr': gmAddressWidgets.manage_addresses,
2097 'meds_substances': gmSubstanceMgmtWidgets.manage_substances,
2098 'meds_doses': gmSubstanceMgmtWidgets.manage_substance_doses,
2099 'meds_components': gmSubstanceMgmtWidgets.manage_drug_components,
2100 'meds_drugs': gmSubstanceMgmtWidgets.manage_drug_products,
2101 'meds_vaccines': gmVaccWidgets.manage_vaccines,
2102 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2103 'communication_channel_types': gmContactWidgets.manage_comm_channel_types,
2104 'billables': gmBillingWidgets.manage_billables,
2105 'ref_data_sources': gmCodingWidgets.browse_data_sources,
2106 'hints': gmAutoHintWidgets.manage_dynamic_hints,
2107 'test_panels': gmMeasurementWidgets.manage_test_panels
2108 }
2109
2110
2111 def edit(item):
2112 try: map_list2handler[item](parent = self)
2113 except KeyError: pass
2114 return False
2115
2116
2117 gmListWidgets.get_choices_from_list (
2118 parent = self,
2119 caption = _('Master data management'),
2120 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2121 data = master_data_lists,
2122 columns = [_('Select the list you want to manage:')],
2123 edit_callback = edit,
2124 single_selection = True,
2125 ignore_OK_button = True
2126 )
2127
2130
2131
2133
2134 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2135 if found:
2136 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2137 return
2138
2139 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2140 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking = False)
2141 return
2142
2143 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2144 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2145 if found:
2146 gmShellAPI.run_command_in_shell(cmd, blocking = False)
2147 return
2148
2149 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2150
2152
2153 curr_pat = gmPerson.gmCurrentPatient()
2154
2155 arriba = gmArriba.cArriba()
2156 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2157 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2158 return
2159
2160
2161 if curr_pat is None:
2162 return
2163
2164 if arriba.pdf_result is None:
2165 return
2166
2167 doc = gmDocumentWidgets.save_file_as_new_document (
2168 parent = self,
2169 filename = arriba.pdf_result,
2170 document_type = _('risk assessment'),
2171 pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit']
2172 )
2173
2174 try: os.remove(arriba.pdf_result)
2175 except Exception: _log.exception('cannot remove [%s]', arriba.pdf_result)
2176
2177 if doc is None:
2178 return
2179
2180 doc['comment'] = 'arriba: %s' % _('cardiovascular risk assessment')
2181 doc.save()
2182
2183 try:
2184 open(arriba.xml_result).close()
2185 part = doc.add_part(file = arriba.xml_result)
2186 except Exception:
2187 _log.exception('error accessing [%s]', arriba.xml_result)
2188 gmDispatcher.send(signal = 'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2189
2190 if part is None:
2191 return
2192
2193 part['obj_comment'] = 'XML-Daten'
2194 part['filename'] = 'arriba-result.xml'
2195 part.save()
2196
2198
2199 dbcfg = gmCfg.cCfgSQL()
2200 cmd = dbcfg.get2 (
2201 option = 'external.tools.acs_risk_calculator_cmd',
2202 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2203 bias = 'user'
2204 )
2205
2206 if cmd is None:
2207 gmDispatcher.send(signal = 'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2208 return
2209
2210 cwd = os.path.expanduser(os.path.join('~', '.gnumed'))
2211 try:
2212 subprocess.check_call (
2213 args = (cmd,),
2214 close_fds = True,
2215 cwd = cwd
2216 )
2217 except (OSError, ValueError, subprocess.CalledProcessError):
2218 _log.exception('there was a problem executing [%s]', cmd)
2219 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2220 return
2221
2222 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2223 for pdf in pdfs:
2224 try:
2225 open(pdf).close()
2226 except:
2227 _log.exception('error accessing [%s]', pdf)
2228 gmDispatcher.send(signal = 'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2229 continue
2230
2231 doc = gmDocumentWidgets.save_file_as_new_document (
2232 parent = self,
2233 filename = pdf,
2234 document_type = 'risk assessment',
2235 pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit']
2236 )
2237
2238 try:
2239 os.remove(pdf)
2240 except Exception:
2241 _log.exception('cannot remove [%s]', pdf)
2242
2243 if doc is None:
2244 continue
2245 doc['comment'] = 'arriba: %s' % _('cardiovascular risk assessment')
2246 doc.save()
2247
2248 return
2249
2250
2258
2261
2264
2267
2284
2285
2288
2289
2295
2296
2299
2300
2301
2302
2305
2306
2309
2310
2313
2314
2315
2316
2325
2326
2328 raise ValueError('raised ValueError to test exception handling')
2329
2330
2332 import faulthandler
2333 _log.debug('testing faulthandler via SIGSEGV')
2334 faulthandler._sigsegv()
2335
2336
2340
2341
2343 raise gmExceptions.AccessDenied (
2344 _('[-9999]: <access violation test error>'),
2345 source = 'GNUmed code',
2346 code = -9999,
2347 details = _('This is a deliberate AccessDenied exception thrown to test the handling of access violations by means of a decorator.')
2348 )
2349
2350 @gmAccessPermissionWidgets.verify_minimum_required_role('admin', activity = _('testing access check for non-existant <admin> role'))
2352 raise gmExceptions.AccessDenied (
2353 _('[-9999]: <access violation test error>'),
2354 source = 'GNUmed code',
2355 code = -9999,
2356 details = _('This is a deliberate AccessDenied exception. You should not see this message because the role is checked in a decorator.')
2357 )
2358
2360 import wx.lib.inspection
2361 wx.lib.inspection.InspectionTool().Show()
2362
2365
2368
2371
2374
2381
2385
2388
2392
2393
2395 self.StatusBar.show_history()
2396
2397
2400
2401
2404
2405
2412
2416
2418 name = os.path.basename(gmLog2._logfile_name)
2419 name, ext = os.path.splitext(name)
2420 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2421 new_path = os.path.expanduser(os.path.join('~', 'gnumed'))
2422
2423 dlg = wx.FileDialog (
2424 parent = self,
2425 message = _("Save current log as..."),
2426 defaultDir = new_path,
2427 defaultFile = new_name,
2428 wildcard = "%s (*.log)|*.log" % _("log files"),
2429 style = wx.FD_SAVE
2430 )
2431 choice = dlg.ShowModal()
2432 new_name = dlg.GetPath()
2433 dlg.DestroyLater()
2434 if choice != wx.ID_OK:
2435 return True
2436
2437 _log.warning('syncing log file for backup to [%s]', new_name)
2438 gmLog2.flush()
2439 shutil.copy2(gmLog2._logfile_name, new_name)
2440 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2441
2444
2445
2448
2449
2452
2453
2456
2457
2458
2459
2461 """This is the wx.EVT_CLOSE handler.
2462
2463 - framework still functional
2464 """
2465 _log.debug('gmTopLevelFrame.OnClose() start')
2466 self._clean_exit()
2467 self.DestroyLater()
2468 _log.debug('gmTopLevelFrame.OnClose() end')
2469 return True
2470
2471
2476
2477
2485
2492
2499
2506
2516
2524
2532
2540
2548
2556
2557
2558 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2567
2568
2569 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2578
2579
2580 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2589
2590
2591 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage family history'))
2600
2601 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage vaccinations'))
2608
2609 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('calculate EDC'))
2613
2614
2615 @gmAccessPermissionWidgets.verify_minimum_required_role('full clinical access', activity = _('manage suppressed hints'))
2622
2623
2630
2631
2648
2651
2654
2655
2657 pat = gmPerson.gmCurrentPatient()
2658 if not pat.connected:
2659 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR. No active patient.'))
2660 return False
2661 from Gnumed.exporters import gmPatientExporter
2662 exporter = gmPatientExporter.cEmrExport(patient = pat)
2663 fname = gmTools.get_unique_filename(prefix = 'gm-exp-', suffix = '.txt')
2664 output_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace')
2665 exporter.set_output_file(output_file)
2666 exporter.dump_constraints()
2667 exporter.dump_demographic_record(True)
2668 exporter.dump_clinical_record()
2669 exporter.dump_med_docs()
2670 output_file.close()
2671 pat.export_area.add_file(filename = fname, hint = _('EMR as text document'))
2672
2673
2691
2692
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2825
2826
2851
2852
2859
2860
2862 curr_pat = gmPerson.gmCurrentPatient()
2863 if not curr_pat.connected:
2864 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2865 return
2866
2867 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2868 if tag is None:
2869 return
2870
2871 tag = curr_pat.add_tag(tag['pk_tag_image'])
2872 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2873 comment = wx.GetTextFromUser (
2874 message = msg,
2875 caption = _('Editing tag comment'),
2876 default_value = gmTools.coalesce(tag['comment'], ''),
2877 parent = self
2878 )
2879
2880 if comment == '':
2881 return
2882
2883 if comment.strip() == tag['comment']:
2884 return
2885
2886 if comment == ' ':
2887 tag['comment'] = None
2888 else:
2889 tag['comment'] = comment.strip()
2890
2891 tag.save()
2892
2893
2903
2904
2914
2915
2924
2925
2934
2935
2937 curr_pat = gmPerson.gmCurrentPatient()
2938 if not curr_pat.connected:
2939 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2940 return False
2941 enc = 'cp850'
2942 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'current-patient.gdt'))
2943 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2944 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2945
2946
2948 curr_pat = gmPerson.gmCurrentPatient()
2949 if not curr_pat.connected:
2950 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as VCARD. No active patient.'))
2951 return False
2952 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'current-patient.vcf'))
2953 curr_pat.export_as_vcard(filename = fname)
2954 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to VCARD file [%s].') % fname)
2955
2956
2959
2960
2963
2964
2967
2968
2971
2974
2982
2990
2993
3000
3004
3007
3010
3011
3014
3015
3018
3019
3022
3023
3025 """Cleanup helper.
3026
3027 - should ALWAYS be called when this program is
3028 to be terminated
3029 - ANY code that should be executed before a
3030 regular shutdown should go in here
3031 - framework still functional
3032 """
3033 _log.debug('gmTopLevelFrame._clean_exit() start')
3034
3035
3036 listener = gmBackendListener.gmBackendListener()
3037 try:
3038 listener.shutdown()
3039 except:
3040 _log.exception('cannot stop backend notifications listener thread')
3041
3042
3043 if _scripting_listener is not None:
3044 try:
3045 _scripting_listener.shutdown()
3046 except:
3047 _log.exception('cannot stop scripting listener thread')
3048
3049
3050 self.StatusBar.clock_update_timer.Stop()
3051 gmTimer.shutdown()
3052 gmPhraseWheel.shutdown()
3053
3054
3055 for call_back in self.__pre_exit_callbacks:
3056 try:
3057 call_back()
3058 except:
3059 print('*** pre-exit callback failed ***')
3060 print('%s' % call_back)
3061 _log.exception('callback [%s] failed', call_back)
3062
3063
3064 gmDispatcher.send('application_closing')
3065
3066
3067 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
3068
3069
3070
3071 curr_width, curr_height = self.GetSize()
3072 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
3073 curr_pos_x, curr_pos_y = self.GetScreenPosition()
3074 _log.info('GUI position at shutdown: [%s:%s]' % (curr_pos_x, curr_pos_y))
3075 if 0 not in [curr_width, curr_height]:
3076 dbcfg = gmCfg.cCfgSQL()
3077 try:
3078 dbcfg.set (
3079 option = 'main.window.width',
3080 value = curr_width,
3081 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
3082 )
3083 dbcfg.set (
3084 option = 'main.window.height',
3085 value = curr_height,
3086 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
3087 )
3088 dbcfg.set (
3089 option = 'main.window.position.x',
3090 value = curr_pos_x,
3091 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
3092 )
3093 dbcfg.set (
3094 option = 'main.window.position.y',
3095 value = curr_pos_y,
3096 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
3097 )
3098 except Exception:
3099 _log.exception('cannot save current client window size and/or position')
3100
3101 if _cfg.get(option = 'debug'):
3102 print('---=== GNUmed shutdown ===---')
3103 try:
3104 print(_('You have to manually close this window to finalize shutting down GNUmed.'))
3105 print(_('This is so that you can inspect the console output at your leisure.'))
3106 except UnicodeEncodeError:
3107 print('You have to manually close this window to finalize shutting down GNUmed.')
3108 print('This is so that you can inspect the console output at your leisure.')
3109 print('---=== GNUmed shutdown ===---')
3110
3111
3112 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
3113
3114
3115 import threading
3116 _log.debug("%s active threads", threading.activeCount())
3117 for t in threading.enumerate():
3118 _log.debug('thread %s', t)
3119 if t.name == 'MainThread':
3120 continue
3121 print('GNUmed: waiting for thread [%s] to finish' % t.name)
3122
3123 _log.debug('gmTopLevelFrame._clean_exit() end')
3124
3125
3126
3127
3129 if _cfg.get(option = 'slave'):
3130 self.__title_template = '%s%s: %%(pat)s [%%(prov)s@%%(wp)s in %%(site)s of %%(prax)s] (%s:%s)' % (
3131 gmTools._GM_TITLE_PREFIX,
3132 gmTools.u_chain,
3133 _cfg.get(option = 'slave personality'),
3134 _cfg.get(option = 'xml-rpc port')
3135 )
3136 else:
3137 self.__title_template = '%s: %%(pat)s [%%(prov)s@%%(wp)s in %%(site)s of %%(prax)s]' % gmTools._GM_TITLE_PREFIX
3138
3139
3141 """Update title of main window based on template.
3142
3143 This gives nice tooltips on iconified GNUmed instances.
3144
3145 User research indicates that in the title bar people want
3146 the date of birth, not the age, so please stick to this
3147 convention.
3148 """
3149 args = {}
3150
3151 pat = gmPerson.gmCurrentPatient()
3152 if pat.connected:
3153 args['pat'] = '%s %s %s (%s) #%d' % (
3154 gmTools.coalesce(pat['title'], '', '%.4s'),
3155 pat['firstnames'],
3156 pat['lastnames'],
3157 pat.get_formatted_dob(format = '%Y %b %d'),
3158 pat['pk_identity']
3159 )
3160 else:
3161 args['pat'] = _('no patient')
3162
3163 args['prov'] = '%s%s.%s' % (
3164 gmTools.coalesce(_provider['title'], '', '%s '),
3165 _provider['firstnames'][:1],
3166 _provider['lastnames']
3167 )
3168
3169 praxis = gmPraxis.gmCurrentPraxisBranch()
3170 args['wp'] = praxis.active_workplace
3171 args['site'] = praxis['branch']
3172 args['prax'] = praxis['praxis']
3173
3174 self.SetTitle(self.__title_template % args)
3175
3176
3184
3185
3190
3191
3193 """Lock GNUmed client against unauthorized access"""
3194
3195
3196
3197 return
3198
3199
3201 """Unlock the main notebook widgets
3202 As long as we are not logged into the database backend,
3203 all pages but the 'login' page of the main notebook widget
3204 are locked; i.e. not accessible by the user
3205 """
3206
3207
3208
3209
3210
3211 return
3212
3214 wx.LayoutAlgorithm().LayoutWindow(self.LayoutMgr, self.nb)
3215
3218
3220 try:
3221 kwargs['style'] = kwargs['style'] | wx.STB_SIZEGRIP | wx.STB_SHOW_TIPS
3222 except KeyError:
3223 kwargs['style'] = wx.STB_SIZEGRIP | wx.STB_SHOW_TIPS
3224 super().__init__(*args, **kwargs)
3225
3226 self.FieldsCount = 2
3227 self.SetStatusWidths([-1, 225])
3228
3229 self.__msg_fifo = []
3230 self.__normal_background_colour = self.GetBackgroundColour()
3231 self.__blink_background_color = 'yellow'
3232 self.__times_to_blink = 0
3233 self.__blink_counter = 0
3234
3235 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
3236 self.clock_update_timer.Start(milliseconds = 1000)
3237
3238 self.Bind(wx.EVT_LEFT_DCLICK, self._on_show_history)
3239
3240
3241 - def SetStatusText(self, text, i=0, beep=False):
3242 prev = self.previous_text
3243 msg = self.__update_history(text, i)
3244 super().SetStatusText(msg, i)
3245 if beep:
3246 wx.Bell()
3247 self.__initiate_blinking(text, i, prev)
3248
3249
3250 - def PushStatusText(self, text, field=0):
3251 prev = self.previous_text
3252 msg = self.__update_history(text, field)
3253 super().PushStatusText(msg, field)
3254 self.__initiate_blinking(text, i, prev)
3255
3256
3259
3260
3261 - def show_history(self):
3262 lines = []
3263 for entry in self.__msg_fifo:
3264 lines.append('%s (%s)' % (entry['text'], ','.join(entry['timestamps'])))
3265 gmGuiHelpers.gm_show_info (
3266 title = _('Statusbar history'),
3267 info = _(
3268 '%s - now\n'
3269 '\n'
3270 '%s'
3271 ) % (
3272 gmDateTime.pydt_now_here().strftime('%H:%M'),
3273 '\n'.join(lines)
3274 )
3275 )
3276
3277
3278
3279
3281 """Advances date and local time in the second slot.
3282
3283 Also drives blinking activity.
3284 """
3285 t = time.localtime(time.time())
3286 st = time.strftime('%Y %b %d %H:%M:%S', t)
3287 self.SetStatusText(st, 1)
3288 if self.__times_to_blink > 0:
3289 self.__perhaps_blink()
3290
3291
3293 if self.__blink_counter > self.__times_to_blink:
3294 self.set_normal_color()
3295 self.__times_to_blink = 0
3296 self.__blink_counter = 0
3297 return
3298
3299 if self.SetBackgroundColour(self.__blink_background_color):
3300 self.__blink_counter += 1
3301 return
3302
3303 self.set_normal_color()
3304
3305
3307 if field != 0:
3308 return
3309 text = text.strip()
3310 if text == '':
3311 return
3312 if text == previous_text:
3313 return
3314 self.__blink_counter = 0
3315 self.__times_to_blink = 2
3316
3317
3319 if len(self.__msg_fifo) == 0:
3320 return None
3321 return self.__msg_fifo[0]['text']
3322
3323 previous_text = property(_get_previous_text)
3324
3325
3326 - def __update_history(self, text, field):
3327 if field > 0:
3328 return text
3329
3330 text = text.strip()
3331 if text == '':
3332 return text
3333
3334 now = gmDateTime.pydt_now_here().strftime('%H:%M')
3335 if len(self.__msg_fifo) == 0:
3336 self.__msg_fifo.append({'text': text, 'timestamps': [now]})
3337 return '%s %s' % (now, text)
3338
3339 last = self.__msg_fifo[0]
3340 if text == last['text']:
3341 last['timestamps'].insert(0, now)
3342 return '%s %s (#%s)' % (now, text, len(last['timestamps']))
3343
3344 self.__msg_fifo.insert(0, {'text': text, 'timestamps': [now]})
3345 if len(self.__msg_fifo) > 20:
3346 self.__msg_fifo = self.__msg_fifo[:20]
3347 return '%s %s' % (now, text)
3348
3349
3350 - def _on_show_history(self, evt):
3352
3353
3355 print('----------------------------------')
3356 print('Statusbar history @ [%s]:' % gmDateTime.pydt_now_here().strftime('%H:%M'))
3357 print('\n'.join(self.__msg_fifo))
3358 print('----------------------------------')
3359
3360
3361 - def _on_print_history(self, evt):
3362 evt.Skip()
3363 self.__print_msg_fifo()
3364
3365
3366 -class gmApp(wx.App):
3367
3369
3370 if _cfg.get(option = 'debug'):
3371 self.SetAssertMode(wx.APP_ASSERT_EXCEPTION | wx.APP_ASSERT_LOG)
3372 else:
3373 self.SetAssertMode(wx.APP_ASSERT_SUPPRESS)
3374
3375 self.__starting_up = True
3376
3377
3378 wx.ToolTip.SetAutoPop(4000)
3379
3380 gmExceptionHandlingWidgets.install_wx_exception_handler()
3381 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
3382
3383 self.SetAppName('gnumed')
3384 self.SetVendorName('gnumed_community')
3385 try:
3386 self.SetAppDisplayName('GNUmed %s' % _cfg.get(option = 'client_version'))
3387 except AttributeError:
3388 _log.info('SetAppDisplayName() not supported')
3389 try:
3390 self.SetVendorDisplayName('The GNUmed Development Community.')
3391 except AttributeError:
3392 _log.info('SetVendorDisplayName() not supported')
3393 paths = gmTools.gmPaths(app_name = 'gnumed', wx = wx)
3394 paths.init_paths(wx = wx, app_name = 'gnumed')
3395
3396
3397 dw, dh = wx.DisplaySize()
3398 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
3399 _log.debug('display size: %s:%s %s mm', dw, dh, wx.DisplaySizeMM())
3400 for disp_idx in range(wx.Display.GetCount()):
3401 disp = wx.Display(disp_idx)
3402 disp_mode = disp.CurrentMode
3403 _log.debug('display [%s] "%s": primary=%s, client_area=%s, geom=%s, vid_mode=[%sbpp across %sx%spx @%sHz]',
3404 disp_idx, disp.Name, disp.IsPrimary(), disp.ClientArea, disp.Geometry,
3405 disp_mode.bpp, disp_mode.Width, disp_mode.Height, disp_mode.refresh
3406 )
3407
3408 if not self.__setup_prefs_file():
3409 return False
3410
3411 gmExceptionHandlingWidgets.set_sender_email(gmPraxis.gmCurrentPraxisBranch().user_email)
3412
3413 self.__guibroker = gmGuiBroker.GuiBroker()
3414 self.__setup_platform()
3415
3416 if not self.__establish_backend_connection():
3417 return False
3418 if not self.__verify_db_account():
3419 return False
3420 if not self.__verify_praxis_branch():
3421 return False
3422
3423 self.__check_db_lang()
3424 self.__update_workplace_list()
3425
3426 if not _cfg.get(option = 'skip-update-check'):
3427 self.__check_for_updates()
3428
3429 if _cfg.get(option = 'slave'):
3430 if not self.__setup_scripting_listener():
3431 return False
3432
3433 frame = gmTopLevelFrame(None, id = -1, title = _('GNUmed client'), size = (640, 440))
3434 frame.CentreOnScreen(wx.BOTH)
3435 self.SetTopWindow(frame)
3436 frame.Show(True)
3437
3438 if _cfg.get(option = 'debug'):
3439 self.RedirectStdio()
3440 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
3441
3442
3443 print('---=== GNUmed startup ===---')
3444 print(_('redirecting STDOUT/STDERR to this log window'))
3445 print('---=== GNUmed startup ===---')
3446
3447 self.__setup_user_activity_timer()
3448 self.__register_events()
3449
3450 wx.CallAfter(self._do_after_init)
3451
3452 return True
3453
3455 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
3456
3457 - after destroying all application windows and controls
3458 - before wx.Windows internal cleanup
3459 """
3460 _log.debug('gmApp.OnExit() start')
3461
3462 self.__shutdown_user_activity_timer()
3463
3464 if _cfg.get(option = 'debug'):
3465 self.RestoreStdio()
3466 sys.stdin = sys.__stdin__
3467 sys.stdout = sys.__stdout__
3468 sys.stderr = sys.__stderr__
3469
3470 top_wins = wx.GetTopLevelWindows()
3471 if len(top_wins) > 0:
3472 _log.debug('%s top level windows still around in <app>.OnExit()', len(top_wins))
3473 _log.debug(top_wins)
3474 for win in top_wins:
3475 _log.debug('destroying: %s', win)
3476 win.DestroyLater()
3477
3478 _log.debug('gmApp.OnExit() end')
3479 return 0
3480
3481
3483 wx.Bell()
3484 wx.Bell()
3485 wx.Bell()
3486 _log.warning('unhandled event detected: QUERY_END_SESSION')
3487 _log.info('we should be saving ourselves from here')
3488 gmLog2.flush()
3489 print('unhandled event detected: QUERY_END_SESSION')
3490
3492 wx.Bell()
3493 wx.Bell()
3494 wx.Bell()
3495 _log.warning('unhandled event detected: END_SESSION')
3496 gmLog2.flush()
3497 print('unhandled event detected: END_SESSION')
3498
3509
3511 self.user_activity_detected = True
3512 evt.Skip()
3513
3515
3516 if self.user_activity_detected:
3517 self.elapsed_inactivity_slices = 0
3518 self.user_activity_detected = False
3519 self.elapsed_inactivity_slices += 1
3520 else:
3521 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
3522
3523 pass
3524
3525 self.user_activity_timer.Start(oneShot = True)
3526
3527
3528
3530 self.__starting_up = False
3531
3532 self.__guibroker['horstspace.top_panel']._TCTRL_patient_selector.SetFocus()
3533 gmHooks.run_hook_script(hook = 'startup-after-GUI-init')
3534
3535
3537 self.user_activity_detected = True
3538 self.elapsed_inactivity_slices = 0
3539
3540 self.max_user_inactivity_slices = 15
3541 self.user_activity_timer = gmTimer.cTimer (
3542 callback = self._on_user_activity_timer_expired,
3543 delay = 2000
3544 )
3545 self.user_activity_timer.Start(oneShot=True)
3546
3547
3549 try:
3550 self.user_activity_timer.Stop()
3551 del self.user_activity_timer
3552 except:
3553 pass
3554
3555
3557 self.Bind(wx.EVT_QUERY_END_SESSION, self._on_query_end_session)
3558 self.Bind(wx.EVT_END_SESSION, self._on_end_session)
3559
3560
3561
3562
3563
3564 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
3565
3566 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
3567 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
3568
3569
3583
3584
3597
3626
3628
3629 if not gmPraxisWidgets.set_active_praxis_branch(no_parent = True):
3630 return False
3631
3632 login = gmPG2.get_default_login()
3633 msg = '\n'
3634 msg += _('Database <%s> on <%s>') % (
3635 login.database,
3636 gmTools.coalesce(login.host, 'localhost')
3637 )
3638 msg += '\n\n'
3639
3640 praxis = gmPraxis.gmCurrentPraxisBranch()
3641 msg += _('Branch "%s" of praxis "%s"\n') % (
3642 praxis['branch'],
3643 praxis['praxis']
3644 )
3645 msg += '\n\n'
3646
3647 banner = praxis.db_logon_banner
3648 if banner.strip() == '':
3649 return True
3650 msg += banner
3651 msg += '\n\n'
3652
3653 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3654 None,
3655 -1,
3656 caption = _('Verifying database'),
3657 question = gmTools.wrap(msg, 60, initial_indent = ' ', subsequent_indent = ' '),
3658 button_defs = [
3659 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3660 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3661 ]
3662 )
3663 log_on = dlg.ShowModal()
3664 dlg.DestroyLater()
3665 if log_on == wx.ID_YES:
3666 return True
3667 _log.info('user decided to not connect to this database')
3668 return False
3669
3683
3685 """Setup access to a config file for storing preferences."""
3686
3687 paths = gmTools.gmPaths(app_name = 'gnumed', wx = wx)
3688
3689 candidates = []
3690 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3691 if explicit_file is not None:
3692 candidates.append(explicit_file)
3693
3694 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3695 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3696 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3697
3698 prefs_file = None
3699 for candidate in candidates:
3700 try:
3701 open(candidate, 'a+').close()
3702 prefs_file = candidate
3703 break
3704 except IOError:
3705 continue
3706
3707 if prefs_file is None:
3708 msg = _(
3709 'Cannot find configuration file in any of:\n'
3710 '\n'
3711 ' %s\n'
3712 'You may need to use the comand line option\n'
3713 '\n'
3714 ' --conf-file=<FILE>'
3715 ) % '\n '.join(candidates)
3716 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3717 return False
3718
3719 _cfg.set_option(option = 'user_preferences_file', value = prefs_file)
3720 _log.info('user preferences file: %s', prefs_file)
3721
3722 return True
3723
3725
3726 from socket import error as SocketError
3727 from Gnumed.pycommon import gmScriptingListener
3728 from Gnumed.wxpython import gmMacro
3729
3730 slave_personality = gmTools.coalesce (
3731 _cfg.get (
3732 group = 'workplace',
3733 option = 'slave personality',
3734 source_order = [
3735 ('explicit', 'return'),
3736 ('workbase', 'return'),
3737 ('user', 'return'),
3738 ('system', 'return')
3739 ]
3740 ),
3741 'gnumed-client'
3742 )
3743 _cfg.set_option(option = 'slave personality', value = slave_personality)
3744
3745
3746 port = int (
3747 gmTools.coalesce (
3748 _cfg.get (
3749 group = 'workplace',
3750 option = 'xml-rpc port',
3751 source_order = [
3752 ('explicit', 'return'),
3753 ('workbase', 'return'),
3754 ('user', 'return'),
3755 ('system', 'return')
3756 ]
3757 ),
3758 9999
3759 )
3760 )
3761 _cfg.set_option(option = 'xml-rpc port', value = port)
3762
3763 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3764 global _scripting_listener
3765 try:
3766 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3767 except SocketError as e:
3768 _log.exception('cannot start GNUmed XML-RPC server')
3769 gmGuiHelpers.gm_show_error (
3770 aMessage = (
3771 'Cannot start the GNUmed server:\n'
3772 '\n'
3773 ' [%s]'
3774 ) % e,
3775 aTitle = _('GNUmed startup')
3776 )
3777 return False
3778
3779 return True
3780
3801
3803 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3804 _log.warning("system locale is undefined (probably meaning 'C')")
3805 return True
3806
3807 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': "select i18n.get_curr_lang() as lang"}])
3808 curr_db_lang = rows[0]['lang']
3809 _log.debug("current database locale: [%s]" % curr_db_lang)
3810
3811 if curr_db_lang is None:
3812
3813 cmd = 'select i18n.set_curr_lang(%s)'
3814 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3815 if len(lang) == 0:
3816 continue
3817 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
3818 if rows[0][0]:
3819 _log.debug("Successfully set database language to [%s]." % lang)
3820 return True
3821 _log.error('Cannot set database language to [%s].' % lang)
3822
3823 return True
3824
3825 if curr_db_lang == gmI18N.system_locale_level['full']:
3826 _log.debug('Database locale (%s) up to date.' % curr_db_lang)
3827 return True
3828 if curr_db_lang == gmI18N.system_locale_level['country']:
3829 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (curr_db_lang, gmI18N.system_locale))
3830 return True
3831 if curr_db_lang == gmI18N.system_locale_level['language']:
3832 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (curr_db_lang, gmI18N.system_locale))
3833 return True
3834
3835 _log.warning('database locale [%s] does not match system locale [%s]' % (curr_db_lang, gmI18N.system_locale))
3836
3837 sys_lang2ignore = _cfg.get (
3838 group = 'backend',
3839 option = 'ignored mismatching system locale',
3840 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3841 )
3842 if gmI18N.system_locale == sys_lang2ignore:
3843 _log.info('configured to ignore system-to-database locale mismatch')
3844 return True
3845
3846
3847 msg = _(
3848 "The currently selected database language ('%s') does\n"
3849 "not match the current system language ('%s').\n"
3850 "\n"
3851 "Do you want to set the database language to '%s' ?\n"
3852 ) % (curr_db_lang, gmI18N.system_locale, gmI18N.system_locale)
3853 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3854 None,
3855 -1,
3856 caption = _('Checking database language settings'),
3857 question = msg,
3858 button_defs = [
3859 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3860 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3861 ],
3862 show_checkbox = True,
3863 checkbox_msg = _('Remember to ignore language mismatch'),
3864 checkbox_tooltip = _(
3865 'Checking this will make GNUmed remember your decision\n'
3866 'until the system language is changed.\n'
3867 '\n'
3868 'You can also reactivate this inquiry by removing the\n'
3869 'corresponding "ignore" option from the configuration file\n'
3870 '\n'
3871 ' [%s]'
3872 ) % _cfg.get(option = 'user_preferences_file')
3873 )
3874 decision = dlg.ShowModal()
3875 remember2ignore_this_mismatch = dlg._CHBOX_dont_ask_again.GetValue()
3876 dlg.DestroyLater()
3877
3878 if decision == wx.ID_NO:
3879 if not remember2ignore_this_mismatch:
3880 return True
3881 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3882 gmCfg2.set_option_in_INI_file (
3883 filename = _cfg.get(option = 'user_preferences_file'),
3884 group = 'backend',
3885 option = 'ignored mismatching system locale',
3886 value = gmI18N.system_locale
3887 )
3888 return True
3889
3890
3891 cmd = 'select i18n.set_curr_lang(%s)'
3892 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3893 if len(lang) == 0:
3894 continue
3895 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
3896 if rows[0][0]:
3897 _log.debug("Successfully set database language to [%s]." % lang)
3898 return True
3899 _log.error('Cannot set database language to [%s].' % lang)
3900
3901
3902 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3903 gmPG2.run_rw_queries(queries = [{
3904 'cmd': 'select i18n.force_curr_lang(%s)',
3905 'args': [gmI18N.system_locale_level['country']]
3906 }])
3907
3908 return True
3909
3912 try:
3913 kwargs['originated_in_database']
3914 print('==> got notification from database "%s":' % kwargs['signal'])
3915 except KeyError:
3916 print('==> received signal from client: "%s"' % kwargs['signal'])
3917
3918 del kwargs['signal']
3919 for key in kwargs:
3920
3921 try: print(' [%s]: %s' % (key, kwargs[key]))
3922 except: print('cannot print signal information')
3923
3928
3940
3945
3948
3949
3950
3951 gmDispatcher.set_main_thread_caller(wx.CallAfter)
3952
3953 if _cfg.get(option = 'debug'):
3954 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3955 _log.debug('gmDispatcher signal monitor activated')
3956
3957 setup_safe_wxEndBusyCursor()
3958
3959 setup_callbacks()
3960
3961
3962
3963
3964 app = gmApp(redirect = False, clearSigInt = False)
3965 app.MainLoop()
3966
3967
3968
3969
3970 if __name__ == '__main__':
3971
3972 from GNUmed.pycommon import gmI18N
3973 gmI18N.activate_locale()
3974 gmI18N.install_domain()
3975
3976 _log.info('Starting up as main module.')
3977 main()
3978
3979
3980