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