1
2 """GNUmed GUI client.
3
4 This contains the GUI application framework and main window
5 of the all signing all dancing GNUmed Python Reference
6 client. It relies on the <gnumed.py> launcher having set up
7 the non-GUI-related runtime environment.
8
9 copyright: authors
10 """
11
12 __author__ = "H. Herb <hherb@gnumed.net>,\
13 K. Hilbert <Karsten.Hilbert@gmx.net>,\
14 I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
15 __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
16
17
18 import sys
19 import time
20 import os
21 import os.path
22 import datetime as pyDT
23 import shutil
24 import logging
25 import urllib2
26 import subprocess
27 import glob
28
29
30
31
32 if not hasattr(sys, 'frozen'):
33 import wxversion
34 wxversion.ensureMinimal('2.8-unicode', optionsRequired=True)
35
36 try:
37 import wx
38 except ImportError:
39 print "GNUmed startup: Cannot import wxPython library."
40 print "GNUmed startup: Make sure wxPython is installed."
41 print 'CRITICAL ERROR: Error importing wxPython. Halted.'
42 raise
43
44
45
46 version = int(u'%s%s' % (wx.MAJOR_VERSION, wx.MINOR_VERSION))
47 if (version < 28) or ('unicode' not in wx.PlatformInfo):
48 print "GNUmed startup: Unsupported wxPython version (%s: %s)." % (wx.VERSION_STRING, wx.PlatformInfo)
49 print "GNUmed startup: wxPython 2.8+ with unicode support is required."
50 print 'CRITICAL ERROR: Proper wxPython version not found. Halted.'
51 raise ValueError('wxPython 2.8+ with unicode support not found')
52
53
54
55 from Gnumed.pycommon import gmCfg
56 from Gnumed.pycommon import gmPG2
57 from Gnumed.pycommon import gmDispatcher
58 from Gnumed.pycommon import gmGuiBroker
59 from Gnumed.pycommon import gmI18N
60 from Gnumed.pycommon import gmExceptions
61 from Gnumed.pycommon import gmShellAPI
62 from Gnumed.pycommon import gmTools
63 from Gnumed.pycommon import gmDateTime
64 from Gnumed.pycommon import gmHooks
65 from Gnumed.pycommon import gmBackendListener
66 from Gnumed.pycommon import gmCfg2
67 from Gnumed.pycommon import gmLog2
68 from Gnumed.pycommon import gmNetworkTools
69
70 from Gnumed.business import gmPerson
71 from Gnumed.business import gmClinicalRecord
72 from Gnumed.business import gmPraxis
73 from Gnumed.business import gmEMRStructItems
74 from Gnumed.business import gmVaccination
75 from Gnumed.business import gmArriba
76 from Gnumed.business import gmStaff
77
78 from Gnumed.exporters import gmPatientExporter
79
80 from Gnumed.wxpython import gmGuiHelpers
81 from Gnumed.wxpython import gmHorstSpace
82 from Gnumed.wxpython import gmEMRBrowser
83 from Gnumed.wxpython import gmDemographicsWidgets
84 from Gnumed.wxpython import gmPersonCreationWidgets
85 from Gnumed.wxpython import gmEMRStructWidgets
86 from Gnumed.wxpython import gmPatSearchWidgets
87 from Gnumed.wxpython import gmAllergyWidgets
88 from Gnumed.wxpython import gmListWidgets
89 from Gnumed.wxpython import gmProviderInboxWidgets
90 from Gnumed.wxpython import gmCfgWidgets
91 from Gnumed.wxpython import gmExceptionHandlingWidgets
92 from Gnumed.wxpython import gmNarrativeWidgets
93 from Gnumed.wxpython import gmPhraseWheel
94 from Gnumed.wxpython import gmMedicationWidgets
95 from Gnumed.wxpython import gmStaffWidgets
96 from Gnumed.wxpython import gmDocumentWidgets
97 from Gnumed.wxpython import gmTimer
98 from Gnumed.wxpython import gmMeasurementWidgets
99 from Gnumed.wxpython import gmFormWidgets
100 from Gnumed.wxpython import gmSnellen
101 from Gnumed.wxpython import gmVaccWidgets
102 from Gnumed.wxpython import gmPersonContactWidgets
103 from Gnumed.wxpython import gmI18nWidgets
104 from Gnumed.wxpython import gmCodingWidgets
105 from Gnumed.wxpython import gmOrganizationWidgets
106 from Gnumed.wxpython import gmAuthWidgets
107 from Gnumed.wxpython import gmFamilyHistoryWidgets
108 from Gnumed.wxpython import gmDataPackWidgets
109 from Gnumed.wxpython import gmContactWidgets
110 from Gnumed.wxpython import gmAddressWidgets
111 from Gnumed.wxpython import gmBillingWidgets
112 from Gnumed.wxpython import gmKeywordExpansionWidgets
113 from Gnumed.wxpython import gmAccessPermissionWidgets
114 from Gnumed.wxpython import gmPraxisWidgets
115
116
117 try:
118 _('dummy-no-need-to-translate-but-make-epydoc-happy')
119 except NameError:
120 _ = lambda x:x
121
122 _cfg = gmCfg2.gmCfgData()
123 _provider = None
124 _scripting_listener = None
125 _original_wxEndBusyCursor = None
126
127 _log = logging.getLogger('gm.main')
128 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
132 """GNUmed client's main windows frame.
133
134 This is where it all happens. Avoid popping up any other windows.
135 Most user interaction should happen to and from widgets within this frame
136 """
137
138 - def __init__(self, parent, id, title, size=wx.DefaultSize):
139 """You'll have to browse the source to understand what the constructor does
140 """
141 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
142
143 self.__setup_font()
144
145 self.__gb = gmGuiBroker.GuiBroker()
146 self.__pre_exit_callbacks = []
147 self.bar_width = -1
148 self.menu_id2plugin = {}
149
150 _log.info('workplace is >>>%s<<<', gmPraxis.gmCurrentPraxisBranch().active_workplace)
151
152 self.__setup_main_menu()
153 self.setup_statusbar()
154 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
155 gmTools.coalesce(_provider['title'], ''),
156 _provider['firstnames'][:1],
157 _provider['lastnames'],
158 _provider['short_alias'],
159 _provider['db_user']
160 ))
161
162 self.__set_window_title_template()
163 self.__update_window_title()
164
165
166
167
168
169 self.SetIcon(gmTools.get_icon(wx = wx))
170
171 self.__register_events()
172
173 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
174 self.vbox = wx.BoxSizer(wx.VERTICAL)
175 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
176
177 self.SetAutoLayout(True)
178 self.SetSizerAndFit(self.vbox)
179
180
181
182
183
184 self.__set_GUI_size()
185
186
188
189 font = self.GetFont()
190 _log.debug('system default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
191
192 desired_font_face = _cfg.get (
193 group = u'workplace',
194 option = u'client font',
195 source_order = [
196 ('explicit', 'return'),
197 ('workbase', 'return'),
198 ('local', 'return'),
199 ('user', 'return'),
200 ('system', 'return')
201 ]
202 )
203
204 fonts2try = []
205 if desired_font_face is not None:
206 _log.info('client is configured to use font [%s]', desired_font_face)
207 fonts2try.append(desired_font_face)
208
209 if wx.Platform == '__WXMSW__':
210 sane_font_face = u'DejaVu Sans'
211 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face)
212 fonts2try.append(sane_font_face)
213
214 if len(fonts2try) == 0:
215 return
216
217 for font_face in fonts2try:
218 success = font.SetFaceName(font_face)
219 if success:
220 self.SetFont(font)
221 _log.debug('switched font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
222 return
223 font = self.GetFont()
224 _log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face)
225
226 return
227
229 """Try to get previous window size from backend."""
230
231 cfg = gmCfg.cCfgSQL()
232
233
234 width = int(cfg.get2 (
235 option = 'main.window.width',
236 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
237 bias = 'workplace',
238 default = 800
239 ))
240
241
242 height = int(cfg.get2 (
243 option = 'main.window.height',
244 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
245 bias = 'workplace',
246 default = 600
247 ))
248
249 dw = wx.DisplaySize()[0]
250 dh = wx.DisplaySize()[1]
251
252 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
253 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
254 _log.debug('previous GUI size [%s:%s]', width, height)
255
256
257 if width > dw:
258 _log.debug('adjusting GUI width from %s to %s', width, dw)
259 width = dw
260
261 if height > dh:
262 _log.debug('adjusting GUI height from %s to %s', height, dh)
263 height = dh
264
265
266 if width < 100:
267 _log.debug('adjusting GUI width to minimum of 100 pixel')
268 width = 100
269 if height < 100:
270 _log.debug('adjusting GUI height to minimum of 100 pixel')
271 height = 100
272
273 _log.info('setting GUI to size [%s:%s]', width, height)
274
275 self.SetClientSize(wx.Size(width, height))
276
278 """Create the main menu entries.
279
280 Individual entries are farmed out to the modules.
281
282 menu item template:
283
284 item = menu_emr_edit.Append(-1, _(''), _(''))
285 self.Bind(wx.EVT_MENU, self__on_, item)
286 """
287 global wx
288 self.mainmenu = wx.MenuBar()
289 self.__gb['main.mainmenu'] = self.mainmenu
290
291
292 menu_gnumed = wx.Menu()
293
294 self.menu_plugins = wx.Menu()
295 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
296
297 ID = wx.NewId()
298 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
299 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
300
301 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
302 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
303
304
305 menu_gnumed.AppendSeparator()
306
307
308 menu_config = wx.Menu()
309
310 item = menu_config.Append(-1, _('All options'), _('List all options as configured in the database.'))
311 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
312
313
314 menu_cfg_db = wx.Menu()
315
316 ID = wx.NewId()
317 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
318 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
319
320 ID = wx.NewId()
321 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
322 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
323
324 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
325
326
327 menu_cfg_client = wx.Menu()
328
329 ID = wx.NewId()
330 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
331 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
332
333 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
334 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
335
336 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
337
338
339 menu_cfg_ui = wx.Menu()
340
341
342 menu_cfg_doc = wx.Menu()
343
344 ID = wx.NewId()
345 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
346 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
347
348 ID = wx.NewId()
349 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
350 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
351
352 ID = wx.NewId()
353 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
354 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
355
356 item = menu_cfg_doc.Append(-1, _('Generate UUID'), _('Whether to generate UUIDs for new documents.'))
357 self.Bind(wx.EVT_MENU, self.__on_configure_generate_doc_uuid, item)
358
359 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
360
361
362 menu_cfg_update = wx.Menu()
363
364 ID = wx.NewId()
365 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
366 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
367
368 ID = wx.NewId()
369 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
370 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
371
372 ID = wx.NewId()
373 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
374 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
375
376 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
377
378
379 menu_cfg_pat_search = wx.Menu()
380
381 ID = wx.NewId()
382 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
383 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
384
385 ID = wx.NewId()
386 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
387 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
388
389 ID = wx.NewId()
390 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
391 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
392
393 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
394 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
395
396 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
397 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
398
399 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
400
401
402 menu_cfg_soap_editing = wx.Menu()
403
404 ID = wx.NewId()
405 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
406 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
407
408 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.'))
409 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item)
410
411 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
412
413 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
414
415
416 menu_cfg_ext_tools = wx.Menu()
417
418
419
420
421
422 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
423 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
424
425 ID = wx.NewId()
426 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
427 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
428
429 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
430 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
431
432 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
433 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
434
435
436
437
438 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
439 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
440
441 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
442 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
443
444 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
445 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
446
447 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
448 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
449
450 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
451
452
453 menu_cfg_bill = wx.Menu()
454
455 item = menu_cfg_bill.Append(-1, _('Invoice template (no VAT)'), _('Select the template for printing an invoice without VAT.'))
456 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_template_no_vat, item)
457
458 item = menu_cfg_bill.Append(-1, _('Invoice template (with VAT)'), _('Select the template for printing an invoice with VAT.'))
459 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_template_with_vat, item)
460
461 item = menu_cfg_bill.Append(-1, _('Catalogs URL'), _('URL for billing catalogs (schedules of fees).'))
462 self.Bind(wx.EVT_MENU, self.__on_configure_billing_catalogs_url, item)
463
464
465 menu_cfg_emr = wx.Menu()
466
467 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
468 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
469
470 item = menu_cfg_emr.Append(-1, _('Prescription mode'), _('Select the default mode for creating a prescription.'))
471 self.Bind(wx.EVT_MENU, self.__on_cfg_prescription_mode, item)
472
473 item = menu_cfg_emr.Append(-1, _('Prescription template'), _('Select the template for printing a prescription.'))
474 self.Bind(wx.EVT_MENU, self.__on_cfg_prescription_template, item)
475
476 item = menu_cfg_emr.Append(-1, _('Default Gnuplot template'), _('Select the default template for plotting test results.'))
477 self.Bind(wx.EVT_MENU, self.__on_cfg_default_gnuplot_template, item)
478
479 item = menu_cfg_emr.Append(-1, _('Fallback provider'), _('Select the doctor to fall back to for patients without a primary provider.'))
480 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
481
482
483 menu_cfg_encounter = wx.Menu()
484
485 ID = wx.NewId()
486 menu_cfg_encounter.Append(ID, _('Edit before patient change'), _('Edit encounter details before change of patient.'))
487 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
488
489 ID = wx.NewId()
490 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
491 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
492
493 ID = wx.NewId()
494 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
495 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
496
497 ID = wx.NewId()
498 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
499 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
500
501 ID = wx.NewId()
502 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
503 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
504
505 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
506
507
508 menu_cfg_episode = wx.Menu()
509
510 ID = wx.NewId()
511 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
512 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
513
514 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
515
516 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
517 menu_config.AppendMenu(wx.NewId(), _('Billing ...'), menu_cfg_bill)
518 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
519
520
521 menu_master_data = wx.Menu()
522
523 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
524 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
525
526 item = menu_master_data.Append(-1, _('Install data packs'), _('Install reference data from data packs.'))
527 self.Bind(wx.EVT_MENU, self.__on_install_data_packs, item)
528
529 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
530 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
531
532 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
533 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
534
535 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
536 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
537
538 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
539
540
541 menu_users = wx.Menu()
542
543 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
544 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
545
546 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
547 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
548
549 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
550 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
551
552 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
553
554
555 menu_gnumed.AppendSeparator()
556
557 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
558 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
559
560 self.mainmenu.Append(menu_gnumed, '&GNUmed')
561
562
563 menu_person = wx.Menu()
564
565 item = menu_person.Append(-1, _('Search'), _('Search for a person.'))
566 self.Bind(wx.EVT_MENU, self.__on_search_person, item)
567 acc_tab = wx.AcceleratorTable([(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, item.GetId())])
568 self.SetAcceleratorTable(acc_tab)
569
570 ID_CREATE_PATIENT = wx.NewId()
571 menu_person.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
572 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
573
574 ID_LOAD_EXT_PAT = wx.NewId()
575 menu_person.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
576 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
577
578 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
579 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
580
581 ID_DEL_PAT = wx.NewId()
582 menu_person.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
583 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
584
585 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
586 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
587
588 menu_person.AppendSeparator()
589
590 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
591 menu_person.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
592 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
593
594
595 ID = wx.NewId()
596 menu_person.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
597 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
598
599 menu_person.AppendSeparator()
600
601 self.mainmenu.Append(menu_person, '&Person')
602 self.__gb['main.patientmenu'] = menu_person
603
604
605 menu_emr = wx.Menu()
606
607
608 menu_emr_edit = wx.Menu()
609
610 item = menu_emr_edit.Append(-1, _('&Past history (health issue / PMH)'), _('Add a past/previous medical history item (health issue) to the EMR of the active patient'))
611 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
612
613 item = menu_emr_edit.Append(-1, _('&Episode'), _('Add an episode of illness to the EMR of the active patient'))
614 self.Bind(wx.EVT_MENU, self.__on_add_episode, item)
615
616 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
617 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
618
619 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
620 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
621
622 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
623 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
624
625 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospitalizations.'))
626 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
627
628 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
629 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
630
631 item = menu_emr_edit.Append(-1, _('&Measurements'), _('Add (a) measurement result(s) for the current patient.'))
632 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
633
634 item = menu_emr_edit.Append(-1, _('&Measurements'), _('Manage measurement results for the current patient.'))
635 self.Bind(wx.EVT_MENU, self.__on_manage_measurements, item)
636
637 item = menu_emr_edit.Append(-1, _('&Vaccinations'), _('Add (a) vaccination(s) for the current patient.'))
638 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
639
640 item = menu_emr_edit.Append(-1, _('&Family history (FHx)'), _('Manage family history.'))
641 self.Bind(wx.EVT_MENU, self.__on_manage_fhx, item)
642
643 item = menu_emr_edit.Append(-1, _('&Encounters'), _('List all encounters including empty ones.'))
644 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
645
646 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
647
648
649 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
650 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
651
652 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
653 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
654
655
656
657
658
659 item = menu_emr.Append(-1, _('Statistics'), _('Show a high-level statistic summary of the EMR.'))
660 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
661
662
663
664
665 menu_emr.AppendSeparator()
666
667
668 menu_emr_export = wx.Menu()
669
670 ID_EXPORT_EMR_ASCII = wx.NewId()
671 menu_emr_export.Append (
672 ID_EXPORT_EMR_ASCII,
673 _('Text document'),
674 _("Export the EMR of the active patient into a text file")
675 )
676 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
677
678 ID_EXPORT_EMR_JOURNAL = wx.NewId()
679 menu_emr_export.Append (
680 ID_EXPORT_EMR_JOURNAL,
681 _('Journal'),
682 _("Export the EMR of the active patient as a chronological journal into a text file")
683 )
684 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
685
686 ID_EXPORT_MEDISTAR = wx.NewId()
687 menu_emr_export.Append (
688 ID_EXPORT_MEDISTAR,
689 _('MEDISTAR import format'),
690 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
691 )
692 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
693
694 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
695
696 menu_emr.AppendSeparator()
697
698 self.mainmenu.Append(menu_emr, _("&EMR"))
699 self.__gb['main.emrmenu'] = menu_emr
700
701
702 menu_paperwork = wx.Menu()
703
704 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
705 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
706
707 menu_paperwork.AppendSeparator()
708
709 item = menu_paperwork.Append(-1, _('List Placeholders'), _('Show a list of all placeholders.'))
710 self.Bind(wx.EVT_MENU, self.__on_show_placeholders, item)
711
712 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
713
714
715 self.menu_tools = wx.Menu()
716
717 item = self.menu_tools.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
718 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
719
720 ID_DICOM_VIEWER = wx.NewId()
721 viewer = _('no viewer installed')
722 if gmShellAPI.detect_external_binary(binary = 'ginkgocadx')[0]:
723 viewer = u'Ginkgo CADx'
724 elif os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
725 viewer = u'OsiriX'
726 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
727 viewer = u'Aeskulap'
728 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
729 viewer = u'AMIDE'
730 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
731 viewer = u'DicomScope'
732 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
733 viewer = u'(x)medcon'
734 self.menu_tools.Append(ID_DICOM_VIEWER, _('DICOM viewer'), _('Start DICOM viewer (%s) for CD-ROM (X-Ray, CT, MR, etc). On Windows just insert CD.') % viewer)
735 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
736 if viewer == _('no viewer installed'):
737 _log.info('neither of Ginkgo CADx / OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
738 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
739
740
741
742
743
744 ID = wx.NewId()
745 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
746 wx.EVT_MENU(self, ID, self.__on_snellen)
747
748 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
749 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
750
751 ID_DICOM_VIEWER = wx.NewId()
752 self.menu_tools.Append(ID_DICOM_VIEWER, u'arriba', _('arriba: cardiovascular risk assessment (%s).') % u'www.arriba-hausarzt.de')
753 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_arriba)
754 if not gmShellAPI.detect_external_binary(binary = 'arriba')[0]:
755 _log.info('<arriba> not found, disabling "arriba" menu item')
756 self.menu_tools.Enable(id = ID_DICOM_VIEWER, enable = False)
757
758
759
760 item = self.menu_tools.Append(-1, u'HL7 (Exelleris)', 'Stage Excelleris HL7')
761 self.Bind(wx.EVT_MENU, self.__on_excelleris, item)
762
763 item = self.menu_tools.Append(-1, u'HL7', 'Stage generic HL7')
764 self.Bind(wx.EVT_MENU, self.__on_hl7, item)
765
766 item = self.menu_tools.Append(-1, u'Incoming', 'Browse incoming data')
767 self.Bind(wx.EVT_MENU, self.__on_incoming, item)
768
769 self.menu_tools.AppendSeparator()
770
771 self.mainmenu.Append(self.menu_tools, _("&Tools"))
772 self.__gb['main.toolsmenu'] = self.menu_tools
773
774
775 menu_knowledge = wx.Menu()
776
777
778 menu_drug_dbs = wx.Menu()
779
780 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
781 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
782
783
784
785
786
787
788 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
789
790 menu_id = wx.NewId()
791 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
792 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
793
794
795
796
797 ID_MEDICAL_LINKS = wx.NewId()
798 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
799 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
800
801 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
802 self.__gb['main.knowledgemenu'] = menu_knowledge
803
804
805 self.menu_office = wx.Menu()
806
807 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
808 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
809
810 self.menu_office.AppendSeparator()
811
812 item = self.menu_office.Append(-1, _('List bills'), _('List all bills across all patients.'))
813 self.Bind(wx.EVT_MENU, self.__on_show_all_bills, item)
814
815 self.mainmenu.Append(self.menu_office, _('&Office'))
816 self.__gb['main.officemenu'] = self.menu_office
817
818
819 help_menu = wx.Menu()
820
821 ID = wx.NewId()
822 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
823 wx.EVT_MENU(self, ID, self.__on_display_wiki)
824
825 ID = wx.NewId()
826 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
827 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
828
829 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
830 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
831
832 item = help_menu.Append(-1, _('&Clear status line'), _('Clear out the status line.'))
833 self.Bind(wx.EVT_MENU, self.__on_clear_status_line, item)
834
835 menu_debugging = wx.Menu()
836
837 ID_SCREENSHOT = wx.NewId()
838 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
839 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
840
841 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
842 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
843
844 ID = wx.NewId()
845 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
846 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
847
848 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
849 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
850
851 ID = wx.NewId()
852 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
853 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
854
855 ID_UNBLOCK = wx.NewId()
856 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
857 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
858
859 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
860 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
861
862
863
864
865 if _cfg.get(option = 'debug'):
866 ID_TOGGLE_PAT_LOCK = wx.NewId()
867 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
868 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
869
870 ID_TEST_EXCEPTION = wx.NewId()
871 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
872 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
873
874 item = menu_debugging.Append(-1, _('Test access violation exception'), _('Simulate an access violation exception.'))
875 self.Bind(wx.EVT_MENU, self.__on_test_access_violation, item)
876
877 item = menu_debugging.Append(-1, _('Test access checking'), _('Simulate a failing access check.'))
878 self.Bind(wx.EVT_MENU, self.__on_test_access_checking, item)
879
880 ID = wx.NewId()
881 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
882 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
883 try:
884 import wx.lib.inspection
885 except ImportError:
886 menu_debugging.Enable(id = ID, enable = False)
887
888 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
889
890 help_menu.AppendSeparator()
891
892 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
893 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
894
895 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
896 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
897
898 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors'))
899 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item)
900
901 help_menu.AppendSeparator()
902
903 self.mainmenu.Append(help_menu, _("&Help"))
904
905 self.__gb['main.helpmenu'] = help_menu
906
907
908 self.SetMenuBar(self.mainmenu)
909
912
913
914
916 """register events we want to react to"""
917
918 wx.EVT_CLOSE(self, self.OnClose)
919 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
920 wx.EVT_END_SESSION(self, self._on_end_session)
921
922 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
923 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
924 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
925 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
926 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
927 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
928 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
929 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
930
931 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
932
933 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
934
935 _log.debug('registering plugin with menu system')
936 _log.debug(' generic name: %s', plugin_name)
937 _log.debug(' class name: %s', class_name)
938 _log.debug(' specific menu: %s', menu_name)
939 _log.debug(' menu item: %s', menu_item_name)
940
941
942 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
943 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
944 self.menu_id2plugin[item.Id] = class_name
945
946
947 if menu_name is not None:
948 menu = self.__gb['main.%smenu' % menu_name]
949 item = menu.Append(-1, menu_item_name, menu_help_string)
950 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
951 self.menu_id2plugin[item.Id] = class_name
952
953 return True
954
956 gmDispatcher.send (
957 signal = u'display_widget',
958 name = self.menu_id2plugin[evt.Id]
959 )
960
962 wx.Bell()
963 wx.Bell()
964 wx.Bell()
965 _log.warning('unhandled event detected: QUERY_END_SESSION')
966 _log.info('we should be saving ourselves from here')
967 gmLog2.flush()
968 print "unhandled event detected: QUERY_END_SESSION"
969
971 wx.Bell()
972 wx.Bell()
973 wx.Bell()
974 _log.warning('unhandled event detected: END_SESSION')
975 gmLog2.flush()
976 print "unhandled event detected: END_SESSION"
977
979 if not callable(callback):
980 raise TypeError(u'callback [%s] not callable' % callback)
981
982 self.__pre_exit_callbacks.append(callback)
983
984 - def _on_set_statustext_pubsub(self, context=None):
985 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
986 wx.CallAfter(self.SetStatusText, msg)
987
988 try:
989 if context.data['beep']:
990 wx.Bell()
991 except KeyError:
992 pass
993
994 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
995
996 if msg is None:
997 msg = _('programmer forgot to specify status message')
998
999 if loglevel is not None:
1000 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
1001
1002 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
1003 wx.CallAfter(self.SetStatusText, msg)
1004
1005 if beep:
1006 wx.Bell()
1007
1009 wx.CallAfter(self.__on_db_maintenance_warning)
1010
1012
1013 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
1014 wx.Bell()
1015 if not wx.GetApp().IsActive():
1016 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
1017
1018 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
1019
1020 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1021 None,
1022 -1,
1023 caption = _('Database shutdown warning'),
1024 question = _(
1025 'The database will be shut down for maintenance\n'
1026 'in a few minutes.\n'
1027 '\n'
1028 'In order to not suffer any loss of data you\n'
1029 'will need to save your current work and log\n'
1030 'out of this GNUmed client.\n'
1031 ),
1032 button_defs = [
1033 {
1034 u'label': _('Close now'),
1035 u'tooltip': _('Close this GNUmed client immediately.'),
1036 u'default': False
1037 },
1038 {
1039 u'label': _('Finish work'),
1040 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
1041 u'default': True
1042 }
1043 ]
1044 )
1045 decision = dlg.ShowModal()
1046 if decision == wx.ID_YES:
1047 top_win = wx.GetApp().GetTopWindow()
1048 wx.CallAfter(top_win.Close)
1049
1051 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
1052
1054
1055 if not wx.GetApp().IsActive():
1056 if urgent:
1057 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
1058 else:
1059 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
1060
1061 if msg is not None:
1062 self.SetStatusText(msg)
1063
1064 if urgent:
1065 wx.Bell()
1066
1067 gmHooks.run_hook_script(hook = u'request_user_attention')
1068
1070 wx.CallAfter(self.__on_pat_name_changed)
1071
1073 self.__update_window_title()
1074
1075 - def _on_post_patient_selection(self, **kwargs):
1076 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
1077
1079 self.__update_window_title()
1080 gmDispatcher.send(signal = 'statustext', msg = u'')
1081 try:
1082 gmHooks.run_hook_script(hook = u'post_patient_activation')
1083 except:
1084 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
1085 raise
1086
1088 return self.__sanity_check_encounter()
1089
1091
1092
1093
1094 if _provider['role'] == u'secretary':
1095 return True
1096
1097 dbcfg = gmCfg.cCfgSQL()
1098 check_enc = bool(dbcfg.get2 (
1099 option = 'encounter.show_editor_before_patient_change',
1100 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1101 bias = 'user',
1102 default = True
1103 ))
1104
1105 if not check_enc:
1106 return True
1107
1108 pat = gmPerson.gmCurrentPatient()
1109 emr = pat.get_emr()
1110 enc = emr.active_encounter
1111
1112
1113 has_narr = enc.has_narrative()
1114 has_docs = enc.has_documents()
1115
1116 if (not has_narr) and (not has_docs):
1117 return True
1118
1119 empty_aoe = (gmTools.coalesce(enc['assessment_of_encounter'], '').strip() == u'')
1120 zero_duration = (enc['last_affirmed'] == enc['started'])
1121
1122
1123 if (not empty_aoe) and (not zero_duration):
1124 return True
1125
1126 if zero_duration:
1127 enc['last_affirmed'] = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
1128
1129
1130 if not has_narr:
1131 if empty_aoe:
1132 enc['assessment_of_encounter'] = _('only documents added')
1133 enc['pk_type'] = gmEMRStructItems.get_encounter_type(description = 'chart review')[0]['pk']
1134
1135 enc.save_payload()
1136 return True
1137
1138
1139 if empty_aoe:
1140
1141 epis = emr.get_episodes_by_encounter()
1142 if len(epis) > 0:
1143 enc_summary = ''
1144 for epi in epis:
1145 enc_summary += '%s; ' % epi['description']
1146 enc['assessment_of_encounter'] = enc_summary
1147
1148 msg = _('Edit the current encounter of the patient you are ABOUT TO LEAVE:')
1149 gmEMRStructWidgets.edit_encounter(parent = self, encounter = enc, msg = msg)
1150
1151 return True
1152
1153
1154
1157
1164
1168
1169
1170
1186
1215
1217 from Gnumed.wxpython import gmAbout
1218 contribs = gmAbout.cContributorsDlg (
1219 parent = self,
1220 id = -1,
1221 title = _('GNUmed contributors'),
1222 size = wx.Size(400,600),
1223 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1224 )
1225 contribs.ShowModal()
1226 del contribs
1227 del gmAbout
1228
1229
1230
1232 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1233 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1234 self.Close(True)
1235 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1236
1239
1241 send = gmGuiHelpers.gm_show_question (
1242 _('This will send a notification about database downtime\n'
1243 'to all GNUmed clients connected to your database.\n'
1244 '\n'
1245 'Do you want to send the notification ?\n'
1246 ),
1247 _('Announcing database maintenance downtime')
1248 )
1249 if not send:
1250 return
1251 gmPG2.send_maintenance_notification()
1252
1253
1256
1257
1258
1271
1272 gmCfgWidgets.configure_string_option (
1273 message = _(
1274 'Some network installations cannot cope with loading\n'
1275 'documents of arbitrary size in one piece from the\n'
1276 'database (mainly observed on older Windows versions)\n.'
1277 '\n'
1278 'Under such circumstances documents need to be retrieved\n'
1279 'in chunks and reassembled on the client.\n'
1280 '\n'
1281 'Here you can set the size (in Bytes) above which\n'
1282 'GNUmed will retrieve documents in chunks. Setting this\n'
1283 'value to 0 will disable the chunking protocol.'
1284 ),
1285 option = 'horstspace.blob_export_chunk_size',
1286 bias = 'workplace',
1287 default_value = 1024 * 1024,
1288 validator = is_valid
1289 )
1290
1291
1292
1360
1364
1365
1366
1375
1376 gmCfgWidgets.configure_string_option (
1377 message = _(
1378 'When GNUmed cannot find an OpenOffice server it\n'
1379 'will try to start one. OpenOffice, however, needs\n'
1380 'some time to fully start up.\n'
1381 '\n'
1382 'Here you can set the time for GNUmed to wait for OOo.\n'
1383 ),
1384 option = 'external.ooo.startup_settle_time',
1385 bias = 'workplace',
1386 default_value = 2.0,
1387 validator = is_valid
1388 )
1389
1392
1407
1408 gmCfgWidgets.configure_string_option (
1409 message = _(
1410 'GNUmed will use this URL to access a website which lets\n'
1411 'you report an adverse drug reaction (ADR).\n'
1412 '\n'
1413 'If you leave this empty it will fall back\n'
1414 'to an URL for reporting ADRs in Germany.'
1415 ),
1416 option = 'external.urls.report_ADR',
1417 bias = 'user',
1418 default_value = german_default,
1419 validator = is_valid
1420 )
1421
1435
1436 gmCfgWidgets.configure_string_option (
1437 message = _(
1438 'GNUmed will use this URL to access a website which lets\n'
1439 'you report an adverse vaccination reaction (vADR).\n'
1440 '\n'
1441 'If you set it to a specific address that URL must be\n'
1442 'accessible now. If you leave it empty it will fall back\n'
1443 'to the URL for reporting other adverse drug reactions.'
1444 ),
1445 option = 'external.urls.report_vaccine_ADR',
1446 bias = 'user',
1447 default_value = german_default,
1448 validator = is_valid
1449 )
1450
1464
1465 gmCfgWidgets.configure_string_option (
1466 message = _(
1467 'GNUmed will use this URL to access an encyclopedia of\n'
1468 'measurement/lab methods from within the measurments grid.\n'
1469 '\n'
1470 'You can leave this empty but to set it to a specific\n'
1471 'address the URL must be accessible now.'
1472 ),
1473 option = 'external.urls.measurements_encyclopedia',
1474 bias = 'user',
1475 default_value = german_default,
1476 validator = is_valid
1477 )
1478
1492
1493 gmCfgWidgets.configure_string_option (
1494 message = _(
1495 'GNUmed will use this URL to access a page showing\n'
1496 'vaccination schedules.\n'
1497 '\n'
1498 'You can leave this empty but to set it to a specific\n'
1499 'address the URL must be accessible now.'
1500 ),
1501 option = 'external.urls.vaccination_plans',
1502 bias = 'user',
1503 default_value = german_default,
1504 validator = is_valid
1505 )
1506
1519
1520 gmCfgWidgets.configure_string_option (
1521 message = _(
1522 'Enter the shell command with which to start the\n'
1523 'the ACS risk assessment calculator.\n'
1524 '\n'
1525 'GNUmed will try to verify the path which may,\n'
1526 'however, fail if you are using an emulator such\n'
1527 'as Wine. Nevertheless, starting the calculator\n'
1528 'will work as long as the shell command is correct\n'
1529 'despite the failing test.'
1530 ),
1531 option = 'external.tools.acs_risk_calculator_cmd',
1532 bias = 'user',
1533 validator = is_valid
1534 )
1535
1538
1551
1552 gmCfgWidgets.configure_string_option (
1553 message = _(
1554 'Enter the shell command with which to start\n'
1555 'the FreeDiams drug database frontend.\n'
1556 '\n'
1557 'GNUmed will try to verify that path.'
1558 ),
1559 option = 'external.tools.freediams_cmd',
1560 bias = 'workplace',
1561 default_value = None,
1562 validator = is_valid
1563 )
1564
1577
1578 gmCfgWidgets.configure_string_option (
1579 message = _(
1580 'Enter the shell command with which to start the\n'
1581 'the IFAP drug database.\n'
1582 '\n'
1583 'GNUmed will try to verify the path which may,\n'
1584 'however, fail if you are using an emulator such\n'
1585 'as Wine. Nevertheless, starting IFAP will work\n'
1586 'as long as the shell command is correct despite\n'
1587 'the failing test.'
1588 ),
1589 option = 'external.ifap-win.shell_command',
1590 bias = 'workplace',
1591 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1592 validator = is_valid
1593 )
1594
1595
1596
1645
1646
1647
1664
1667
1670
1675
1676 gmCfgWidgets.configure_string_option (
1677 message = _(
1678 'When a patient is activated GNUmed checks the\n'
1679 "proximity of the patient's birthday.\n"
1680 '\n'
1681 'If the birthday falls within the range of\n'
1682 ' "today %s <the interval you set here>"\n'
1683 'GNUmed will remind you of the recent or\n'
1684 'imminent anniversary.'
1685 ) % u'\u2213',
1686 option = u'patient_search.dob_warn_interval',
1687 bias = 'user',
1688 default_value = '1 week',
1689 validator = is_valid
1690 )
1691
1693
1694 gmCfgWidgets.configure_boolean_option (
1695 parent = self,
1696 question = _(
1697 'When adding progress notes do you want to\n'
1698 'allow opening several unassociated, new\n'
1699 'episodes for a patient at once ?\n'
1700 '\n'
1701 'This can be particularly helpful when entering\n'
1702 'progress notes on entirely new patients presenting\n'
1703 'with a multitude of problems on their first visit.'
1704 ),
1705 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1706 button_tooltips = [
1707 _('Yes, allow for multiple new episodes concurrently.'),
1708 _('No, only allow editing one new episode at a time.')
1709 ]
1710 )
1711
1713
1714 gmCfgWidgets.configure_boolean_option (
1715 parent = self,
1716 question = _(
1717 'When activating a patient, do you want GNUmed to\n'
1718 'auto-open editors for all active problems that were\n'
1719 'touched upon during the current and the most recent\n'
1720 'encounter ?'
1721 ),
1722 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1723 button_tooltips = [
1724 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1725 _('No, only auto-open one editor for a new, unassociated problem.')
1726 ]
1727 )
1728
1774
1775
1776
1779
1782
1795
1796 gmCfgWidgets.configure_string_option (
1797 message = _(
1798 'GNUmed will use this URL to let you browse\n'
1799 'billing catalogs (schedules of fees).\n'
1800 '\n'
1801 'You can leave this empty but to set it to a specific\n'
1802 'address the URL must be accessible now.'
1803 ),
1804 option = 'external.urls.schedules_of_fees',
1805 bias = 'user',
1806 default_value = german_default,
1807 validator = is_valid
1808 )
1809
1810
1811
1814
1817
1819 gmCfgWidgets.configure_string_from_list_option (
1820 parent = self,
1821 message = _('Select the default prescription mode.\n'),
1822 option = 'horst_space.default_prescription_mode',
1823 bias = 'user',
1824 default_value = u'form',
1825 choices = [ _('Formular'), _('Datenbank') ],
1826 columns = [_('Prescription mode')],
1827 data = [ u'form', u'database' ]
1828 )
1829
1832
1835
1849
1851 gmCfgWidgets.configure_boolean_option (
1852 parent = self,
1853 question = _(
1854 'Do you want GNUmed to show the encounter\n'
1855 'details editor when changing the active patient ?'
1856 ),
1857 option = 'encounter.show_editor_before_patient_change',
1858 button_tooltips = [
1859 _('Yes, show the encounter editor if it seems appropriate.'),
1860 _('No, never show the encounter editor even if it would seem useful.')
1861 ]
1862 )
1863
1868
1869 gmCfgWidgets.configure_string_option (
1870 message = _(
1871 'When a patient is activated GNUmed checks the\n'
1872 'chart for encounters lacking any entries.\n'
1873 '\n'
1874 'Any such encounters older than what you set\n'
1875 'here will be removed from the medical record.\n'
1876 '\n'
1877 'To effectively disable removal of such encounters\n'
1878 'set this option to an improbable value.\n'
1879 ),
1880 option = 'encounter.ttl_if_empty',
1881 bias = 'user',
1882 default_value = '1 week',
1883 validator = is_valid
1884 )
1885
1890
1891 gmCfgWidgets.configure_string_option (
1892 message = _(
1893 'When a patient is activated GNUmed checks the\n'
1894 'age of the most recent encounter.\n'
1895 '\n'
1896 'If that encounter is younger than this age\n'
1897 'the existing encounter will be continued.\n'
1898 '\n'
1899 '(If it is really old a new encounter is\n'
1900 ' started, or else GNUmed will ask you.)\n'
1901 ),
1902 option = 'encounter.minimum_ttl',
1903 bias = 'user',
1904 default_value = '1 hour 30 minutes',
1905 validator = is_valid
1906 )
1907
1912
1913 gmCfgWidgets.configure_string_option (
1914 message = _(
1915 'When a patient is activated GNUmed checks the\n'
1916 'age of the most recent encounter.\n'
1917 '\n'
1918 'If that encounter is older than this age\n'
1919 'GNUmed will always start a new encounter.\n'
1920 '\n'
1921 '(If it is very recent the existing encounter\n'
1922 ' is continued, or else GNUmed will ask you.)\n'
1923 ),
1924 option = 'encounter.maximum_ttl',
1925 bias = 'user',
1926 default_value = '6 hours',
1927 validator = is_valid
1928 )
1929
1938
1939 gmCfgWidgets.configure_string_option (
1940 message = _(
1941 'At any time there can only be one open (ongoing)\n'
1942 'episode for each health issue.\n'
1943 '\n'
1944 'When you try to open (add data to) an episode on a health\n'
1945 'issue GNUmed will check for an existing open episode on\n'
1946 'that issue. If there is any it will check the age of that\n'
1947 'episode. The episode is closed if it has been dormant (no\n'
1948 'data added, that is) for the period of time (in days) you\n'
1949 'set here.\n'
1950 '\n'
1951 "If the existing episode hasn't been dormant long enough\n"
1952 'GNUmed will consult you what to do.\n'
1953 '\n'
1954 'Enter maximum episode dormancy in DAYS:'
1955 ),
1956 option = 'episode.ttl',
1957 bias = 'user',
1958 default_value = 60,
1959 validator = is_valid
1960 )
1961
1992
2007
2032
2044
2045 gmCfgWidgets.configure_string_option (
2046 message = _(
2047 'GNUmed can check for new releases being available. To do\n'
2048 'so it needs to load version information from an URL.\n'
2049 '\n'
2050 'The default URL is:\n'
2051 '\n'
2052 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
2053 '\n'
2054 'but you can configure any other URL locally. Note\n'
2055 'that you must enter the location as a valid URL.\n'
2056 'Depending on the URL the client will need online\n'
2057 'access when checking for updates.'
2058 ),
2059 option = u'horstspace.update.url',
2060 bias = u'workplace',
2061 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
2062 validator = is_valid
2063 )
2064
2082
2099
2116
2127
2128 gmCfgWidgets.configure_string_option (
2129 message = _(
2130 'GNUmed can show the document review dialog after\n'
2131 'calling the appropriate viewer for that document.\n'
2132 '\n'
2133 'Select the conditions under which you want\n'
2134 'GNUmed to do so:\n'
2135 '\n'
2136 ' 0: never display the review dialog\n'
2137 ' 1: always display the dialog\n'
2138 ' 2: only if there is no previous review by me\n'
2139 ' 3: only if there is no previous review at all\n'
2140 ' 4: only if there is no review by the responsible reviewer\n'
2141 '\n'
2142 'Note that if a viewer is configured to not block\n'
2143 'GNUmed during document display the review dialog\n'
2144 'will actually appear in parallel to the viewer.'
2145 ),
2146 option = u'horstspace.document_viewer.review_after_display',
2147 bias = u'user',
2148 default_value = 3,
2149 validator = is_valid
2150 )
2151
2153
2154
2155 master_data_lists = [
2156 'adr',
2157 'billables',
2158 'drugs',
2159 'hints',
2160 'codes',
2161 'communication_channel_types',
2162 'substances_in_brands',
2163 'substances',
2164 'labs',
2165 'form_templates',
2166 'doc_types',
2167 'enc_types',
2168 'text_expansions',
2169 'meta_test_types',
2170 'orgs',
2171 'patient_tags',
2172 'provinces',
2173 'db_translations',
2174 'ref_data_sources',
2175 'test_types',
2176 'test_panels',
2177 'vacc_indications',
2178 'vaccines',
2179 'workplaces'
2180 ]
2181
2182 master_data_list_names = {
2183 'adr': _('Addresses (likely slow)'),
2184 'drugs': _('Branded drugs (as marketed)'),
2185 'hints': _('Clinical hints'),
2186 'codes': _('Codes and their respective terms'),
2187 'communication_channel_types': _('Communication channel types'),
2188 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2189 'labs': _('Diagnostic organizations (path labs, ...)'),
2190 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2191 'doc_types': _('Document types'),
2192 'enc_types': _('Encounter types'),
2193 'text_expansions': _('Keyword based text expansion macros'),
2194 'meta_test_types': _('Meta test/measurement types'),
2195 'orgs': _('Organizations with their units, addresses, and comm channels'),
2196 'patient_tags': _('Patient tags'),
2197 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2198 'db_translations': _('String translations in the database'),
2199 'test_types': _('Test/measurement types'),
2200 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2201 'vaccines': _('Vaccines'),
2202 'workplaces': _('Workplace profiles (which plugins to load)'),
2203 'substances': _('Consumable substances'),
2204 'billables': _('Billable items'),
2205 'ref_data_sources': _('Reference data sources'),
2206 'test_panels': _('Test/measurement panels/profiles')
2207 }
2208
2209 map_list2handler = {
2210 'form_templates': gmFormWidgets.manage_form_templates,
2211 'doc_types': gmDocumentWidgets.manage_document_types,
2212 'text_expansions': gmKeywordExpansionWidgets.configure_keyword_text_expansion,
2213 'db_translations': gmI18nWidgets.manage_translations,
2214 'codes': gmCodingWidgets.browse_coded_terms,
2215 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2216 'provinces': gmAddressWidgets.manage_provinces,
2217 'workplaces': gmPraxisWidgets.configure_workplace_plugins,
2218 'drugs': gmMedicationWidgets.manage_branded_drugs,
2219 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2220 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2221 'test_types': gmMeasurementWidgets.manage_measurement_types,
2222 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2223 'vaccines': gmVaccWidgets.manage_vaccines,
2224 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2225 'orgs': gmOrganizationWidgets.manage_orgs,
2226 'adr': gmAddressWidgets.manage_addresses,
2227 'substances': gmMedicationWidgets.manage_consumable_substances,
2228 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2229 'communication_channel_types': gmContactWidgets.manage_comm_channel_types,
2230 'billables': gmBillingWidgets.manage_billables,
2231 'ref_data_sources': gmCodingWidgets.browse_data_sources,
2232 'hints': gmProviderInboxWidgets.browse_dynamic_hints,
2233 'test_panels': gmMeasurementWidgets.manage_test_panels
2234 }
2235
2236
2237 def edit(item):
2238 try: map_list2handler[item](parent = self)
2239 except KeyError: pass
2240 return False
2241
2242
2243 gmListWidgets.get_choices_from_list (
2244 parent = self,
2245 caption = _('Master data management'),
2246 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2247 data = master_data_lists,
2248 columns = [_('Select the list you want to manage:')],
2249 edit_callback = edit,
2250 single_selection = True,
2251 ignore_OK_button = True
2252 )
2253
2255
2256 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2257 if found:
2258 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2259 return
2260
2261 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2262 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking = False)
2263 return
2264
2265 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2266 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2267 if found:
2268 gmShellAPI.run_command_in_shell(cmd, blocking = False)
2269 return
2270
2271 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2272
2274
2275 curr_pat = gmPerson.gmCurrentPatient()
2276
2277 arriba = gmArriba.cArriba()
2278 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2279 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2280 return
2281
2282
2283 if curr_pat is None:
2284 return
2285
2286 if arriba.pdf_result is None:
2287 return
2288
2289 doc = gmDocumentWidgets.save_file_as_new_document (
2290 parent = self,
2291 filename = arriba.pdf_result,
2292 document_type = _('risk assessment')
2293 )
2294
2295 try: os.remove(arriba.pdf_result)
2296 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2297
2298 if doc is None:
2299 return
2300
2301 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2302 doc.save()
2303
2304 try:
2305 open(arriba.xml_result).close()
2306 part = doc.add_part(file = arriba.xml_result)
2307 except StandardError:
2308 _log.exception('error accessing [%s]', arriba.xml_result)
2309 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2310
2311 if part is None:
2312 return
2313
2314 part['obj_comment'] = u'XML-Daten'
2315 part['filename'] = u'arriba-result.xml'
2316 part.save()
2317
2319
2320 dbcfg = gmCfg.cCfgSQL()
2321 cmd = dbcfg.get2 (
2322 option = u'external.tools.acs_risk_calculator_cmd',
2323 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2324 bias = 'user'
2325 )
2326
2327 if cmd is None:
2328 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2329 return
2330
2331 cwd = os.path.expanduser(os.path.join('~', '.gnumed'))
2332 try:
2333 subprocess.check_call (
2334 args = (cmd,),
2335 close_fds = True,
2336 cwd = cwd
2337 )
2338 except (OSError, ValueError, subprocess.CalledProcessError):
2339 _log.exception('there was a problem executing [%s]', cmd)
2340 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2341 return
2342
2343 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2344 for pdf in pdfs:
2345 try:
2346 open(pdf).close()
2347 except:
2348 _log.exception('error accessing [%s]', pdf)
2349 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2350 continue
2351
2352 doc = gmDocumentWidgets.save_file_as_new_document (
2353 parent = self,
2354 filename = pdf,
2355 document_type = u'risk assessment'
2356 )
2357
2358 try:
2359 os.remove(pdf)
2360 except StandardError:
2361 _log.exception('cannot remove [%s]', pdf)
2362
2363 if doc is None:
2364 continue
2365 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2366 doc.save()
2367
2368 return
2369
2372
2375
2378
2380 dlg = gmSnellen.cSnellenCfgDlg()
2381 if dlg.ShowModal() != wx.ID_OK:
2382 return
2383
2384 frame = gmSnellen.cSnellenChart (
2385 width = dlg.vals[0],
2386 height = dlg.vals[1],
2387 alpha = dlg.vals[2],
2388 mirr = dlg.vals[3],
2389 parent = None
2390 )
2391 frame.CentreOnScreen(wx.BOTH)
2392
2393
2394 frame.Show(True)
2395
2396
2399
2402
2405
2406
2407
2411
2414
2415
2416
2418 wx.CallAfter(self.__save_screenshot)
2419 evt.Skip()
2420
2422
2423 time.sleep(0.5)
2424
2425 rect = self.GetRect()
2426
2427
2428 if sys.platform == 'linux2':
2429 client_x, client_y = self.ClientToScreen((0, 0))
2430 border_width = client_x - rect.x
2431 title_bar_height = client_y - rect.y
2432
2433 if self.GetMenuBar():
2434 title_bar_height /= 2
2435 rect.width += (border_width * 2)
2436 rect.height += title_bar_height + border_width
2437
2438 wdc = wx.ScreenDC()
2439 mdc = wx.MemoryDC()
2440 img = wx.EmptyBitmap(rect.width, rect.height)
2441 mdc.SelectObject(img)
2442 mdc.Blit (
2443 0, 0,
2444 rect.width, rect.height,
2445 wdc,
2446 rect.x, rect.y
2447 )
2448
2449
2450 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2451 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2452 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2453
2455
2456 raise ValueError('raised ValueError to test exception handling')
2457
2459 raise gmExceptions.AccessDenied (
2460 _('[-9999]: <access violation test error>'),
2461 source = u'GNUmed code',
2462 code = -9999,
2463 details = _('This is a deliberate AcessDenied exception thrown to test the handling of access violations by means of a decorator.')
2464 )
2465
2466 @gmAccessPermissionWidgets.verify_minimum_required_role('admin', activity = _('testing access check for non-existant <admin> role'))
2468 raise gmExceptions.AccessDenied (
2469 _('[-9999]: <access violation test error>'),
2470 source = u'GNUmed code',
2471 code = -9999,
2472 details = _('This is a deliberate AcessDenied exception. You should not see this message because the role is checked in a decorator.')
2473 )
2474
2476 import wx.lib.inspection
2477 wx.lib.inspection.InspectionTool().Show()
2478
2481
2484
2487
2490
2497
2501
2504
2507
2514
2519
2521 name = os.path.basename(gmLog2._logfile_name)
2522 name, ext = os.path.splitext(name)
2523 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2524 new_path = os.path.expanduser(os.path.join('~', 'gnumed'))
2525
2526 dlg = wx.FileDialog (
2527 parent = self,
2528 message = _("Save current log as..."),
2529 defaultDir = new_path,
2530 defaultFile = new_name,
2531 wildcard = "%s (*.log)|*.log" % _("log files"),
2532 style = wx.SAVE
2533 )
2534 choice = dlg.ShowModal()
2535 new_name = dlg.GetPath()
2536 dlg.Destroy()
2537 if choice != wx.ID_OK:
2538 return True
2539
2540 _log.warning('syncing log file for backup to [%s]', new_name)
2541 gmLog2.flush()
2542 shutil.copy2(gmLog2._logfile_name, new_name)
2543 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2544
2547
2548
2549
2551 """This is the wx.EVT_CLOSE handler.
2552
2553 - framework still functional
2554 """
2555 _log.debug('gmTopLevelFrame.OnClose() start')
2556 self._clean_exit()
2557 self.Destroy()
2558 _log.debug('gmTopLevelFrame.OnClose() end')
2559 return True
2560
2566
2571
2579
2586
2593
2600
2610
2618
2626
2634
2642
2643 @gmAccessPermissionWidgets.verify_minimum_required_role('doctor', activity = _('manage vaccinations'))
2652
2661
2669
2676
2693
2696
2699
2701
2702 pat = gmPerson.gmCurrentPatient()
2703 if not pat.connected:
2704 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2705 return False
2706
2707 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2708 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', pat['dirname']))
2709 gmTools.mkdir(aDefDir)
2710
2711 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2712 dlg = wx.FileDialog (
2713 parent = self,
2714 message = _("Save patient's EMR journal as..."),
2715 defaultDir = aDefDir,
2716 defaultFile = fname,
2717 wildcard = aWildcard,
2718 style = wx.SAVE
2719 )
2720 choice = dlg.ShowModal()
2721 fname = dlg.GetPath()
2722 dlg.Destroy()
2723 if choice != wx.ID_OK:
2724 return True
2725
2726 _log.debug('exporting EMR journal to [%s]' % fname)
2727
2728 exporter = gmPatientExporter.cEMRJournalExporter()
2729
2730 wx.BeginBusyCursor()
2731 try:
2732 fname = exporter.export_to_file(filename = fname)
2733 except:
2734 wx.EndBusyCursor()
2735 gmGuiHelpers.gm_show_error (
2736 _('Error exporting patient EMR as chronological journal.'),
2737 _('EMR journal export')
2738 )
2739 raise
2740 wx.EndBusyCursor()
2741
2742 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2743
2744 return True
2745
2752
2754 curr_pat = gmPerson.gmCurrentPatient()
2755 if not curr_pat.connected:
2756 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2757 return
2758
2759 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2760 if tag is None:
2761 return
2762
2763 tag = curr_pat.add_tag(tag['pk_tag_image'])
2764 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2765 comment = wx.GetTextFromUser (
2766 message = msg,
2767 caption = _('Editing tag comment'),
2768 default_value = gmTools.coalesce(tag['comment'], u''),
2769 parent = self
2770 )
2771
2772 if comment == u'':
2773 return
2774
2775 if comment.strip() == tag['comment']:
2776 return
2777
2778 if comment == u' ':
2779 tag['comment'] = None
2780 else:
2781 tag['comment'] = comment.strip()
2782
2783 tag.save()
2784
2794
2796 curr_pat = gmPerson.gmCurrentPatient()
2797 if not curr_pat.connected:
2798 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2799 return False
2800 enc = 'cp850'
2801 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'current-patient.gdt'))
2802 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2803 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2804
2807
2810
2818
2826
2829
2836
2840
2843
2846
2849
2852
2857
2859 """Cleanup helper.
2860
2861 - should ALWAYS be called when this program is
2862 to be terminated
2863 - ANY code that should be executed before a
2864 regular shutdown should go in here
2865 - framework still functional
2866 """
2867 _log.debug('gmTopLevelFrame._clean_exit() start')
2868
2869
2870 listener = gmBackendListener.gmBackendListener()
2871 try:
2872 listener.shutdown()
2873 except:
2874 _log.exception('cannot stop backend notifications listener thread')
2875
2876
2877 if _scripting_listener is not None:
2878 try:
2879 _scripting_listener.shutdown()
2880 except:
2881 _log.exception('cannot stop scripting listener thread')
2882
2883
2884 self.clock_update_timer.Stop()
2885 gmTimer.shutdown()
2886 gmPhraseWheel.shutdown()
2887
2888
2889 for call_back in self.__pre_exit_callbacks:
2890 try:
2891 call_back()
2892 except:
2893 print "*** pre-exit callback failed ***"
2894 print call_back
2895 _log.exception('callback [%s] failed', call_back)
2896
2897
2898 gmDispatcher.send(u'application_closing')
2899
2900
2901 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2902
2903
2904 curr_width, curr_height = self.GetClientSizeTuple()
2905 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2906 dbcfg = gmCfg.cCfgSQL()
2907 dbcfg.set (
2908 option = 'main.window.width',
2909 value = curr_width,
2910 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
2911 )
2912 dbcfg.set (
2913 option = 'main.window.height',
2914 value = curr_height,
2915 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
2916 )
2917
2918 if _cfg.get(option = 'debug'):
2919 print '---=== GNUmed shutdown ===---'
2920 try:
2921 print _('You have to manually close this window to finalize shutting down GNUmed.')
2922 print _('This is so that you can inspect the console output at your leisure.')
2923 except UnicodeEncodeError:
2924 print 'You have to manually close this window to finalize shutting down GNUmed.'
2925 print 'This is so that you can inspect the console output at your leisure.'
2926 print '---=== GNUmed shutdown ===---'
2927
2928
2929 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2930
2931
2932 import threading
2933 _log.debug("%s active threads", threading.activeCount())
2934 for t in threading.enumerate():
2935 _log.debug('thread %s', t)
2936
2937 _log.debug('gmTopLevelFrame._clean_exit() end')
2938
2939
2940
2942
2943 if _cfg.get(option = 'slave'):
2944 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s in %%(site)s of %%(prax)s] (%s:%s)' % (
2945 _cfg.get(option = 'slave personality'),
2946 _cfg.get(option = 'xml-rpc port')
2947 )
2948 else:
2949 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s in %(site)s of %(prax)s]'
2950
2952 """Update title of main window based on template.
2953
2954 This gives nice tooltips on iconified GNUmed instances.
2955
2956 User research indicates that in the title bar people want
2957 the date of birth, not the age, so please stick to this
2958 convention.
2959 """
2960 args = {}
2961
2962 pat = gmPerson.gmCurrentPatient()
2963 if pat.connected:
2964 args['pat'] = u'%s %s %s (%s) #%d' % (
2965 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2966 pat['firstnames'],
2967 pat['lastnames'],
2968 pat.get_formatted_dob(format = '%Y %b %d', encoding = gmI18N.get_encoding()),
2969 pat['pk_identity']
2970 )
2971 else:
2972 args['pat'] = _('no patient')
2973
2974 args['prov'] = u'%s%s.%s' % (
2975 gmTools.coalesce(_provider['title'], u'', u'%s '),
2976 _provider['firstnames'][:1],
2977 _provider['lastnames']
2978 )
2979
2980 praxis = gmPraxis.gmCurrentPraxisBranch()
2981 args['wp'] = praxis.active_workplace
2982 args['site'] = praxis.branch['branch']
2983 args['prax'] = praxis.branch['praxis']
2984
2985 self.SetTitle(self.__title_template % args)
2986
2987
2989 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2990 sb.SetStatusWidths([-1, 225])
2991
2992 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2993 self._cb_update_clock()
2994
2995 self.clock_update_timer.Start(milliseconds = 1000)
2996
2998 """Displays date and local time in the second slot of the status bar"""
2999 t = time.localtime(time.time())
3000 st = time.strftime('%Y %b %d %H:%M:%S', t).decode(gmI18N.get_encoding(), 'replace')
3001 self.SetStatusText(st, 1)
3002
3004 """Lock GNUmed client against unauthorized access"""
3005
3006
3007
3008 return
3009
3011 """Unlock the main notebook widgets
3012 As long as we are not logged into the database backend,
3013 all pages but the 'login' page of the main notebook widget
3014 are locked; i.e. not accessible by the user
3015 """
3016
3017
3018
3019
3020
3021 return
3022
3024 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
3025
3027
3029
3030 self.__starting_up = True
3031
3032 gmExceptionHandlingWidgets.install_wx_exception_handler()
3033 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
3034
3035
3036 self.SetAppName(u'gnumed')
3037 self.SetVendorName(u'The GNUmed Development Community.')
3038 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3039 paths.init_paths(wx = wx, app_name = u'gnumed')
3040
3041 if not self.__setup_prefs_file():
3042 return False
3043
3044 gmExceptionHandlingWidgets.set_sender_email(gmPraxis.gmCurrentPraxisBranch().user_email)
3045
3046 self.__guibroker = gmGuiBroker.GuiBroker()
3047 self.__setup_platform()
3048
3049 if not self.__establish_backend_connection():
3050 return False
3051 if not self.__verify_db_account():
3052 return False
3053 if not self.__verify_praxis_branch():
3054 return False
3055
3056 self.__check_db_lang()
3057 self.__update_workplace_list()
3058
3059 if not _cfg.get(option = 'skip-update-check'):
3060 self.__check_for_updates()
3061
3062 if _cfg.get(option = 'slave'):
3063 if not self.__setup_scripting_listener():
3064 return False
3065
3066
3067 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
3068 frame.CentreOnScreen(wx.BOTH)
3069 self.SetTopWindow(frame)
3070 frame.Show(True)
3071
3072 if _cfg.get(option = 'debug'):
3073 self.RedirectStdio()
3074 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
3075
3076
3077 print '---=== GNUmed startup ===---'
3078 print _('redirecting STDOUT/STDERR to this log window')
3079 print '---=== GNUmed startup ===---'
3080
3081 self.__setup_user_activity_timer()
3082 self.__register_events()
3083
3084 wx.CallAfter(self._do_after_init)
3085
3086 return True
3087
3089 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
3090
3091 - after destroying all application windows and controls
3092 - before wx.Windows internal cleanup
3093 """
3094 _log.debug('gmApp.OnExit() start')
3095
3096 self.__shutdown_user_activity_timer()
3097
3098 if _cfg.get(option = 'debug'):
3099 self.RestoreStdio()
3100 sys.stdin = sys.__stdin__
3101 sys.stdout = sys.__stdout__
3102 sys.stderr = sys.__stderr__
3103
3104 _log.debug('gmApp.OnExit() end')
3105
3107 wx.Bell()
3108 wx.Bell()
3109 wx.Bell()
3110 _log.warning('unhandled event detected: QUERY_END_SESSION')
3111 _log.info('we should be saving ourselves from here')
3112 gmLog2.flush()
3113 print "unhandled event detected: QUERY_END_SESSION"
3114
3116 wx.Bell()
3117 wx.Bell()
3118 wx.Bell()
3119 _log.warning('unhandled event detected: END_SESSION')
3120 gmLog2.flush()
3121 print "unhandled event detected: END_SESSION"
3122
3133
3135 self.user_activity_detected = True
3136 evt.Skip()
3137
3139
3140 if self.user_activity_detected:
3141 self.elapsed_inactivity_slices = 0
3142 self.user_activity_detected = False
3143 self.elapsed_inactivity_slices += 1
3144 else:
3145 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
3146
3147 pass
3148
3149 self.user_activity_timer.Start(oneShot = True)
3150
3151
3152
3154 try:
3155 kwargs['originated_in_database']
3156 print '==> got notification from database "%s":' % kwargs['signal']
3157 except KeyError:
3158 print '==> received signal from client: "%s"' % kwargs['signal']
3159
3160 del kwargs['signal']
3161 for key in kwargs.keys():
3162 print ' [%s]: %s' % (key, kwargs[key])
3163
3170
3172 self.user_activity_detected = True
3173 self.elapsed_inactivity_slices = 0
3174
3175 self.max_user_inactivity_slices = 15
3176 self.user_activity_timer = gmTimer.cTimer (
3177 callback = self._on_user_activity_timer_expired,
3178 delay = 2000
3179 )
3180 self.user_activity_timer.Start(oneShot=True)
3181
3183 try:
3184 self.user_activity_timer.Stop()
3185 del self.user_activity_timer
3186 except:
3187 pass
3188
3190 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
3191 wx.EVT_END_SESSION(self, self._on_end_session)
3192
3193
3194
3195
3196
3197 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
3198
3199 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
3200 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
3201
3202 if _cfg.get(option = 'debug'):
3203 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
3204 _log.debug('connected signal monitor')
3205
3221
3234
3263
3265
3266 if not gmPraxisWidgets.set_active_praxis_branch(no_parent = True):
3267 return False
3268
3269 login = gmPG2.get_default_login()
3270 msg = u'\n'
3271 msg += _('Database <%s> on <%s>') % (
3272 login.database,
3273 gmTools.coalesce(login.host, u'localhost')
3274 )
3275 msg += u'\n\n'
3276
3277 praxis = gmPraxis.gmCurrentPraxisBranch()
3278 msg += _('Branch "%s" of praxis "%s"\n') % (
3279 praxis.branch['branch'],
3280 praxis.branch['praxis']
3281 )
3282 msg += u'\n\n'
3283
3284 banner = praxis.db_logon_banner
3285 if banner.strip() == u'':
3286 return True
3287 msg += banner
3288 msg += u'\n\n'
3289
3290 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3291 None,
3292 -1,
3293 caption = _('Verifying database'),
3294 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3295 button_defs = [
3296 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3297 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3298 ]
3299 )
3300 log_on = dlg.ShowModal()
3301 dlg.Destroy()
3302 if log_on == wx.ID_YES:
3303 return True
3304 _log.info('user decided to not connect to this database')
3305 return False
3306
3320
3322 """Setup access to a config file for storing preferences."""
3323
3324 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3325
3326 candidates = []
3327 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3328 if explicit_file is not None:
3329 candidates.append(explicit_file)
3330
3331 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3332 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3333 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3334
3335 prefs_file = None
3336 for candidate in candidates:
3337 try:
3338 open(candidate, 'a+').close()
3339 prefs_file = candidate
3340 break
3341 except IOError:
3342 continue
3343
3344 if prefs_file is None:
3345 msg = _(
3346 'Cannot find configuration file in any of:\n'
3347 '\n'
3348 ' %s\n'
3349 'You may need to use the comand line option\n'
3350 '\n'
3351 ' --conf-file=<FILE>'
3352 ) % '\n '.join(candidates)
3353 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3354 return False
3355
3356 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3357 _log.info('user preferences file: %s', prefs_file)
3358
3359 return True
3360
3362
3363 from socket import error as SocketError
3364 from Gnumed.pycommon import gmScriptingListener
3365 from Gnumed.wxpython import gmMacro
3366
3367 slave_personality = gmTools.coalesce (
3368 _cfg.get (
3369 group = u'workplace',
3370 option = u'slave personality',
3371 source_order = [
3372 ('explicit', 'return'),
3373 ('workbase', 'return'),
3374 ('user', 'return'),
3375 ('system', 'return')
3376 ]
3377 ),
3378 u'gnumed-client'
3379 )
3380 _cfg.set_option(option = 'slave personality', value = slave_personality)
3381
3382
3383 port = int (
3384 gmTools.coalesce (
3385 _cfg.get (
3386 group = u'workplace',
3387 option = u'xml-rpc port',
3388 source_order = [
3389 ('explicit', 'return'),
3390 ('workbase', 'return'),
3391 ('user', 'return'),
3392 ('system', 'return')
3393 ]
3394 ),
3395 9999
3396 )
3397 )
3398 _cfg.set_option(option = 'xml-rpc port', value = port)
3399
3400 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3401 global _scripting_listener
3402 try:
3403 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3404 except SocketError, e:
3405 _log.exception('cannot start GNUmed XML-RPC server')
3406 gmGuiHelpers.gm_show_error (
3407 aMessage = (
3408 'Cannot start the GNUmed server:\n'
3409 '\n'
3410 ' [%s]'
3411 ) % e,
3412 aTitle = _('GNUmed startup')
3413 )
3414 return False
3415
3416 return True
3417
3438
3440 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3441 _log.warning("system locale is undefined (probably meaning 'C')")
3442 return True
3443
3444 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3445 curr_db_lang = rows[0]['lang']
3446 _log.debug("current database locale: [%s]" % curr_db_lang)
3447
3448 if curr_db_lang is None:
3449
3450 cmd = u'select i18n.set_curr_lang(%s)'
3451 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3452 if len(lang) == 0:
3453 continue
3454 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
3455 if rows[0][0]:
3456 _log.debug("Successfully set database language to [%s]." % lang)
3457 return True
3458 _log.error('Cannot set database language to [%s].' % lang)
3459
3460 return True
3461
3462 if curr_db_lang == gmI18N.system_locale_level['full']:
3463 _log.debug('Database locale (%s) up to date.' % curr_db_lang)
3464 return True
3465 if curr_db_lang == gmI18N.system_locale_level['country']:
3466 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (curr_db_lang, gmI18N.system_locale))
3467 return True
3468 if curr_db_lang == gmI18N.system_locale_level['language']:
3469 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (curr_db_lang, gmI18N.system_locale))
3470 return True
3471
3472 _log.warning('database locale [%s] does not match system locale [%s]' % (curr_db_lang, gmI18N.system_locale))
3473
3474 sys_lang2ignore = _cfg.get (
3475 group = u'backend',
3476 option = u'ignored mismatching system locale',
3477 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3478 )
3479 if gmI18N.system_locale == sys_lang2ignore:
3480 _log.info('configured to ignore system-to-database locale mismatch')
3481 return True
3482
3483
3484 msg = _(
3485 "The currently selected database language ('%s') does\n"
3486 "not match the current system language ('%s').\n"
3487 "\n"
3488 "Do you want to set the database language to '%s' ?\n"
3489 ) % (curr_db_lang, gmI18N.system_locale, gmI18N.system_locale)
3490 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3491 None,
3492 -1,
3493 caption = _('Checking database language settings'),
3494 question = msg,
3495 button_defs = [
3496 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3497 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3498 ],
3499 show_checkbox = True,
3500 checkbox_msg = _('Remember to ignore language mismatch'),
3501 checkbox_tooltip = _(
3502 'Checking this will make GNUmed remember your decision\n'
3503 'until the system language is changed.\n'
3504 '\n'
3505 'You can also reactivate this inquiry by removing the\n'
3506 'corresponding "ignore" option from the configuration file\n'
3507 '\n'
3508 ' [%s]'
3509 ) % _cfg.get(option = 'user_preferences_file')
3510 )
3511 decision = dlg.ShowModal()
3512 remember2ignore_this_mismatch = dlg._CHBOX_dont_ask_again.GetValue()
3513 dlg.Destroy()
3514
3515 if decision == wx.ID_NO:
3516 if not remember2ignore_this_mismatch:
3517 return True
3518 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3519 gmCfg2.set_option_in_INI_file (
3520 filename = _cfg.get(option = 'user_preferences_file'),
3521 group = 'backend',
3522 option = 'ignored mismatching system locale',
3523 value = gmI18N.system_locale
3524 )
3525 return True
3526
3527
3528 cmd = u'select i18n.set_curr_lang(%s)'
3529 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3530 if len(lang) == 0:
3531 continue
3532 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
3533 if rows[0][0]:
3534 _log.debug("Successfully set database language to [%s]." % lang)
3535 return True
3536 _log.error('Cannot set database language to [%s].' % lang)
3537
3538
3539 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3540 gmPG2.run_rw_queries(queries = [{
3541 'cmd': u'select i18n.force_curr_lang(%s)',
3542 'args': [gmI18N.system_locale_level['country']]
3543 }])
3544
3545 return True
3546
3548 try:
3549 kwargs['originated_in_database']
3550 print '==> got notification from database "%s":' % kwargs['signal']
3551 except KeyError:
3552 print '==> received signal from client: "%s"' % kwargs['signal']
3553
3554 del kwargs['signal']
3555 for key in kwargs.keys():
3556
3557 try: print ' [%s]: %s' % (key, kwargs[key])
3558 except: print 'cannot print signal information'
3559
3563
3574
3576
3577 if _cfg.get(option = 'debug'):
3578 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3579 _log.debug('gmDispatcher signal monitor activated')
3580
3581 setup_safe_wxEndBusyCursor()
3582
3583 wx.InitAllImageHandlers()
3584
3585
3586
3587 app = gmApp(redirect = False, clearSigInt = False)
3588 app.MainLoop()
3589
3590
3591
3592 if __name__ == '__main__':
3593
3594 from GNUmed.pycommon import gmI18N
3595 gmI18N.activate_locale()
3596 gmI18N.install_domain()
3597
3598 _log.info('Starting up as main module.')
3599 main()
3600
3601
3602