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, _('Manage praxis'), _('Manage your praxis branches.'))
527 self.Bind(wx.EVT_MENU, self.__on_manage_praxis, item)
528
529 item = menu_master_data.Append(-1, _('Install data packs'), _('Install reference data from data packs.'))
530 self.Bind(wx.EVT_MENU, self.__on_install_data_packs, item)
531
532 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
533 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
534
535 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
536 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
537
538 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
539 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
540
541 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
542
543
544 menu_users = wx.Menu()
545
546 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
547 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
548
549 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
550 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
551
552 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
553 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
554
555 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
556
557
558 menu_gnumed.AppendSeparator()
559
560 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
561 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
562
563 self.mainmenu.Append(menu_gnumed, '&GNUmed')
564
565
566 menu_person = wx.Menu()
567
568 item = menu_person.Append(-1, _('Search'), _('Search for a person.'))
569 self.Bind(wx.EVT_MENU, self.__on_search_person, item)
570 acc_tab = wx.AcceleratorTable([(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, item.GetId())])
571 self.SetAcceleratorTable(acc_tab)
572
573 ID_CREATE_PATIENT = wx.NewId()
574 menu_person.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
575 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
576
577 ID_LOAD_EXT_PAT = wx.NewId()
578 menu_person.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
579 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
580
581 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
582 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
583
584 ID_DEL_PAT = wx.NewId()
585 menu_person.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
586 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
587
588 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
589 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
590
591 menu_person.AppendSeparator()
592
593 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
594 menu_person.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
595 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
596
597
598 ID = wx.NewId()
599 menu_person.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
600 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
601
602 menu_person.AppendSeparator()
603
604 self.mainmenu.Append(menu_person, '&Person')
605 self.__gb['main.patientmenu'] = menu_person
606
607
608 menu_emr = wx.Menu()
609
610
611 menu_emr_edit = wx.Menu()
612
613 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'))
614 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
615
616 item = menu_emr_edit.Append(-1, _('&Episode'), _('Add an episode of illness to the EMR of the active patient'))
617 self.Bind(wx.EVT_MENU, self.__on_add_episode, item)
618
619 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
620 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
621
622 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
623 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
624
625 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
626 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
627
628 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospitalizations.'))
629 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
630
631 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
632 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, 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, _('&Bills'), _('List all bills across all patients.'))
813 self.Bind(wx.EVT_MENU, self.__on_show_all_bills, item)
814
815 item = self.menu_office.Append(-1, _('&Organizations'), _('Manage organizations.'))
816 self.Bind(wx.EVT_MENU, self.__on_manage_orgs, item)
817
818 self.mainmenu.Append(self.menu_office, _('&Office'))
819 self.__gb['main.officemenu'] = self.menu_office
820
821
822 help_menu = wx.Menu()
823
824 ID = wx.NewId()
825 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
826 wx.EVT_MENU(self, ID, self.__on_display_wiki)
827
828 ID = wx.NewId()
829 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
830 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
831
832 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
833 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
834
835 item = help_menu.Append(-1, _('&Clear status line'), _('Clear out the status line.'))
836 self.Bind(wx.EVT_MENU, self.__on_clear_status_line, item)
837
838 menu_debugging = wx.Menu()
839
840 ID_SCREENSHOT = wx.NewId()
841 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
842 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
843
844 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
845 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
846
847 ID = wx.NewId()
848 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
849 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
850
851 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
852 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
853
854 ID = wx.NewId()
855 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
856 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
857
858 ID_UNBLOCK = wx.NewId()
859 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
860 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
861
862 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
863 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
864
865
866
867
868 if _cfg.get(option = 'debug'):
869 ID_TOGGLE_PAT_LOCK = wx.NewId()
870 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
871 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
872
873 ID_TEST_EXCEPTION = wx.NewId()
874 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
875 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
876
877 item = menu_debugging.Append(-1, _('Test access violation exception'), _('Simulate an access violation exception.'))
878 self.Bind(wx.EVT_MENU, self.__on_test_access_violation, item)
879
880 item = menu_debugging.Append(-1, _('Test access checking'), _('Simulate a failing access check.'))
881 self.Bind(wx.EVT_MENU, self.__on_test_access_checking, item)
882
883 ID = wx.NewId()
884 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
885 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
886 try:
887 import wx.lib.inspection
888 except ImportError:
889 menu_debugging.Enable(id = ID, enable = False)
890
891 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
892
893 help_menu.AppendSeparator()
894
895 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
896 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
897
898 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
899 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
900
901 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors'))
902 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item)
903
904 help_menu.AppendSeparator()
905
906 self.mainmenu.Append(help_menu, _("&Help"))
907
908 self.__gb['main.helpmenu'] = help_menu
909
910
911 self.SetMenuBar(self.mainmenu)
912
915
916
917
919 """register events we want to react to"""
920
921 wx.EVT_CLOSE(self, self.OnClose)
922 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
923 wx.EVT_END_SESSION(self, self._on_end_session)
924
925 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
926 gmDispatcher.connect(signal = u'dem.names_mod_db', receiver = self._on_pat_name_changed)
927 gmDispatcher.connect(signal = u'dem.identity_mod_db', receiver = self._on_pat_name_changed)
928 gmDispatcher.connect(signal = u'dem.praxis_branch_mod_db', receiver = self._on_pat_name_changed)
929 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
930 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
931
932 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
933 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
934 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
935
936 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
937
938 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
939
940 _log.debug('registering plugin with menu system')
941 _log.debug(' generic name: %s', plugin_name)
942 _log.debug(' class name: %s', class_name)
943 _log.debug(' specific menu: %s', menu_name)
944 _log.debug(' menu item: %s', menu_item_name)
945
946
947 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
948 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
949 self.menu_id2plugin[item.Id] = class_name
950
951
952 if menu_name is not None:
953 menu = self.__gb['main.%smenu' % menu_name]
954 item = menu.Append(-1, menu_item_name, menu_help_string)
955 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
956 self.menu_id2plugin[item.Id] = class_name
957
958 return True
959
961 gmDispatcher.send (
962 signal = u'display_widget',
963 name = self.menu_id2plugin[evt.Id]
964 )
965
967 wx.Bell()
968 wx.Bell()
969 wx.Bell()
970 _log.warning('unhandled event detected: QUERY_END_SESSION')
971 _log.info('we should be saving ourselves from here')
972 gmLog2.flush()
973 print "unhandled event detected: QUERY_END_SESSION"
974
976 wx.Bell()
977 wx.Bell()
978 wx.Bell()
979 _log.warning('unhandled event detected: END_SESSION')
980 gmLog2.flush()
981 print "unhandled event detected: END_SESSION"
982
984 if not callable(callback):
985 raise TypeError(u'callback [%s] not callable' % callback)
986
987 self.__pre_exit_callbacks.append(callback)
988
989 - def _on_set_statustext_pubsub(self, context=None):
990 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
991 wx.CallAfter(self.SetStatusText, msg)
992
993 try:
994 if context.data['beep']:
995 wx.Bell()
996 except KeyError:
997 pass
998
999 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
1000
1001 if msg is None:
1002 msg = _('programmer forgot to specify status message')
1003
1004 if loglevel is not None:
1005 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
1006
1007 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
1008 wx.CallAfter(self.SetStatusText, msg)
1009
1010 if beep:
1011 wx.Bell()
1012
1014 wx.CallAfter(self.__on_db_maintenance_warning)
1015
1017
1018 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
1019 wx.Bell()
1020 if not wx.GetApp().IsActive():
1021 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
1022
1023 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
1024
1025 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1026 None,
1027 -1,
1028 caption = _('Database shutdown warning'),
1029 question = _(
1030 'The database will be shut down for maintenance\n'
1031 'in a few minutes.\n'
1032 '\n'
1033 'In order to not suffer any loss of data you\n'
1034 'will need to save your current work and log\n'
1035 'out of this GNUmed client.\n'
1036 ),
1037 button_defs = [
1038 {
1039 u'label': _('Close now'),
1040 u'tooltip': _('Close this GNUmed client immediately.'),
1041 u'default': False
1042 },
1043 {
1044 u'label': _('Finish work'),
1045 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
1046 u'default': True
1047 }
1048 ]
1049 )
1050 decision = dlg.ShowModal()
1051 if decision == wx.ID_YES:
1052 top_win = wx.GetApp().GetTopWindow()
1053 wx.CallAfter(top_win.Close)
1054
1056 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
1057
1059
1060 if not wx.GetApp().IsActive():
1061 if urgent:
1062 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
1063 else:
1064 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
1065
1066 if msg is not None:
1067 self.SetStatusText(msg)
1068
1069 if urgent:
1070 wx.Bell()
1071
1072 gmHooks.run_hook_script(hook = u'request_user_attention')
1073
1075 wx.CallAfter(self.__on_pat_name_changed)
1076
1078 self.__update_window_title()
1079
1080 - def _on_post_patient_selection(self, **kwargs):
1081 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
1082
1084 self.__update_window_title()
1085 gmDispatcher.send(signal = 'statustext', msg = u'')
1086 try:
1087 gmHooks.run_hook_script(hook = u'post_patient_activation')
1088 except:
1089 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
1090 raise
1091
1093 return self.__sanity_check_encounter()
1094
1096
1097
1098
1099 if _provider['role'] == u'secretary':
1100 return True
1101
1102 dbcfg = gmCfg.cCfgSQL()
1103 check_enc = bool(dbcfg.get2 (
1104 option = 'encounter.show_editor_before_patient_change',
1105 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1106 bias = 'user',
1107 default = True
1108 ))
1109
1110 if not check_enc:
1111 return True
1112
1113 pat = gmPerson.gmCurrentPatient()
1114 emr = pat.get_emr()
1115 enc = emr.active_encounter
1116
1117
1118 has_narr = enc.has_narrative()
1119 has_docs = enc.has_documents()
1120
1121 if (not has_narr) and (not has_docs):
1122 return True
1123
1124 empty_aoe = (gmTools.coalesce(enc['assessment_of_encounter'], '').strip() == u'')
1125 zero_duration = (enc['last_affirmed'] == enc['started'])
1126
1127
1128 if (not empty_aoe) and (not zero_duration):
1129 return True
1130
1131 if zero_duration:
1132 enc['last_affirmed'] = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
1133
1134
1135 if not has_narr:
1136 if empty_aoe:
1137 enc['assessment_of_encounter'] = _('only documents added')
1138 enc['pk_type'] = gmEMRStructItems.get_encounter_type(description = 'chart review')[0]['pk']
1139
1140 enc.save_payload()
1141 return True
1142
1143
1144 if empty_aoe:
1145
1146 epis = emr.get_episodes_by_encounter()
1147 if len(epis) > 0:
1148 enc_summary = ''
1149 for epi in epis:
1150 enc_summary += '%s; ' % epi['description']
1151 enc['assessment_of_encounter'] = enc_summary
1152
1153 msg = _('Edit the current encounter of the patient you are ABOUT TO LEAVE:')
1154 gmEMRStructWidgets.edit_encounter(parent = self, encounter = enc, msg = msg)
1155
1156 return True
1157
1158
1159
1162
1169
1173
1174
1175
1191
1220
1222 from Gnumed.wxpython import gmAbout
1223 contribs = gmAbout.cContributorsDlg (
1224 parent = self,
1225 id = -1,
1226 title = _('GNUmed contributors'),
1227 size = wx.Size(400,600),
1228 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1229 )
1230 contribs.ShowModal()
1231 del contribs
1232 del gmAbout
1233
1234
1235
1237 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1238 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1239 self.Close(True)
1240 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1241
1244
1246 send = gmGuiHelpers.gm_show_question (
1247 _('This will send a notification about database downtime\n'
1248 'to all GNUmed clients connected to your database.\n'
1249 '\n'
1250 'Do you want to send the notification ?\n'
1251 ),
1252 _('Announcing database maintenance downtime')
1253 )
1254 if not send:
1255 return
1256 gmPG2.send_maintenance_notification()
1257
1258
1261
1262
1263
1276
1277 gmCfgWidgets.configure_string_option (
1278 message = _(
1279 'Some network installations cannot cope with loading\n'
1280 'documents of arbitrary size in one piece from the\n'
1281 'database (mainly observed on older Windows versions)\n.'
1282 '\n'
1283 'Under such circumstances documents need to be retrieved\n'
1284 'in chunks and reassembled on the client.\n'
1285 '\n'
1286 'Here you can set the size (in Bytes) above which\n'
1287 'GNUmed will retrieve documents in chunks. Setting this\n'
1288 'value to 0 will disable the chunking protocol.'
1289 ),
1290 option = 'horstspace.blob_export_chunk_size',
1291 bias = 'workplace',
1292 default_value = 1024 * 1024,
1293 validator = is_valid
1294 )
1295
1296
1297
1365
1369
1370
1371
1380
1381 gmCfgWidgets.configure_string_option (
1382 message = _(
1383 'When GNUmed cannot find an OpenOffice server it\n'
1384 'will try to start one. OpenOffice, however, needs\n'
1385 'some time to fully start up.\n'
1386 '\n'
1387 'Here you can set the time for GNUmed to wait for OOo.\n'
1388 ),
1389 option = 'external.ooo.startup_settle_time',
1390 bias = 'workplace',
1391 default_value = 2.0,
1392 validator = is_valid
1393 )
1394
1397
1412
1413 gmCfgWidgets.configure_string_option (
1414 message = _(
1415 'GNUmed will use this URL to access a website which lets\n'
1416 'you report an adverse drug reaction (ADR).\n'
1417 '\n'
1418 'If you leave this empty it will fall back\n'
1419 'to an URL for reporting ADRs in Germany.'
1420 ),
1421 option = 'external.urls.report_ADR',
1422 bias = 'user',
1423 default_value = german_default,
1424 validator = is_valid
1425 )
1426
1440
1441 gmCfgWidgets.configure_string_option (
1442 message = _(
1443 'GNUmed will use this URL to access a website which lets\n'
1444 'you report an adverse vaccination reaction (vADR).\n'
1445 '\n'
1446 'If you set it to a specific address that URL must be\n'
1447 'accessible now. If you leave it empty it will fall back\n'
1448 'to the URL for reporting other adverse drug reactions.'
1449 ),
1450 option = 'external.urls.report_vaccine_ADR',
1451 bias = 'user',
1452 default_value = german_default,
1453 validator = is_valid
1454 )
1455
1469
1470 gmCfgWidgets.configure_string_option (
1471 message = _(
1472 'GNUmed will use this URL to access an encyclopedia of\n'
1473 'measurement/lab methods from within the measurments grid.\n'
1474 '\n'
1475 'You can leave this empty but to set it to a specific\n'
1476 'address the URL must be accessible now.'
1477 ),
1478 option = 'external.urls.measurements_encyclopedia',
1479 bias = 'user',
1480 default_value = german_default,
1481 validator = is_valid
1482 )
1483
1497
1498 gmCfgWidgets.configure_string_option (
1499 message = _(
1500 'GNUmed will use this URL to access a page showing\n'
1501 'vaccination schedules.\n'
1502 '\n'
1503 'You can leave this empty but to set it to a specific\n'
1504 'address the URL must be accessible now.'
1505 ),
1506 option = 'external.urls.vaccination_plans',
1507 bias = 'user',
1508 default_value = german_default,
1509 validator = is_valid
1510 )
1511
1524
1525 gmCfgWidgets.configure_string_option (
1526 message = _(
1527 'Enter the shell command with which to start the\n'
1528 'the ACS risk assessment calculator.\n'
1529 '\n'
1530 'GNUmed will try to verify the path which may,\n'
1531 'however, fail if you are using an emulator such\n'
1532 'as Wine. Nevertheless, starting the calculator\n'
1533 'will work as long as the shell command is correct\n'
1534 'despite the failing test.'
1535 ),
1536 option = 'external.tools.acs_risk_calculator_cmd',
1537 bias = 'user',
1538 validator = is_valid
1539 )
1540
1543
1556
1557 gmCfgWidgets.configure_string_option (
1558 message = _(
1559 'Enter the shell command with which to start\n'
1560 'the FreeDiams drug database frontend.\n'
1561 '\n'
1562 'GNUmed will try to verify that path.'
1563 ),
1564 option = 'external.tools.freediams_cmd',
1565 bias = 'workplace',
1566 default_value = None,
1567 validator = is_valid
1568 )
1569
1582
1583 gmCfgWidgets.configure_string_option (
1584 message = _(
1585 'Enter the shell command with which to start the\n'
1586 'the IFAP drug database.\n'
1587 '\n'
1588 'GNUmed will try to verify the path which may,\n'
1589 'however, fail if you are using an emulator such\n'
1590 'as Wine. Nevertheless, starting IFAP will work\n'
1591 'as long as the shell command is correct despite\n'
1592 'the failing test.'
1593 ),
1594 option = 'external.ifap-win.shell_command',
1595 bias = 'workplace',
1596 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1597 validator = is_valid
1598 )
1599
1600
1601
1650
1651
1652
1669
1672
1675
1680
1681 gmCfgWidgets.configure_string_option (
1682 message = _(
1683 'When a patient is activated GNUmed checks the\n'
1684 "proximity of the patient's birthday.\n"
1685 '\n'
1686 'If the birthday falls within the range of\n'
1687 ' "today %s <the interval you set here>"\n'
1688 'GNUmed will remind you of the recent or\n'
1689 'imminent anniversary.'
1690 ) % u'\u2213',
1691 option = u'patient_search.dob_warn_interval',
1692 bias = 'user',
1693 default_value = '1 week',
1694 validator = is_valid
1695 )
1696
1698
1699 gmCfgWidgets.configure_boolean_option (
1700 parent = self,
1701 question = _(
1702 'When adding progress notes do you want to\n'
1703 'allow opening several unassociated, new\n'
1704 'episodes for a patient at once ?\n'
1705 '\n'
1706 'This can be particularly helpful when entering\n'
1707 'progress notes on entirely new patients presenting\n'
1708 'with a multitude of problems on their first visit.'
1709 ),
1710 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1711 button_tooltips = [
1712 _('Yes, allow for multiple new episodes concurrently.'),
1713 _('No, only allow editing one new episode at a time.')
1714 ]
1715 )
1716
1718
1719 gmCfgWidgets.configure_boolean_option (
1720 parent = self,
1721 question = _(
1722 'When activating a patient, do you want GNUmed to\n'
1723 'auto-open editors for all active problems that were\n'
1724 'touched upon during the current and the most recent\n'
1725 'encounter ?'
1726 ),
1727 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1728 button_tooltips = [
1729 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1730 _('No, only auto-open one editor for a new, unassociated problem.')
1731 ]
1732 )
1733
1779
1780
1781
1784
1787
1800
1801 gmCfgWidgets.configure_string_option (
1802 message = _(
1803 'GNUmed will use this URL to let you browse\n'
1804 'billing catalogs (schedules of fees).\n'
1805 '\n'
1806 'You can leave this empty but to set it to a specific\n'
1807 'address the URL must be accessible now.'
1808 ),
1809 option = 'external.urls.schedules_of_fees',
1810 bias = 'user',
1811 default_value = german_default,
1812 validator = is_valid
1813 )
1814
1815
1816
1819
1822
1824 gmCfgWidgets.configure_string_from_list_option (
1825 parent = self,
1826 message = _('Select the default prescription mode.\n'),
1827 option = 'horst_space.default_prescription_mode',
1828 bias = 'user',
1829 default_value = u'form',
1830 choices = [ _('Formular'), _('Datenbank') ],
1831 columns = [_('Prescription mode')],
1832 data = [ u'form', u'database' ]
1833 )
1834
1837
1840
1842 enc_types = gmEMRStructItems.get_encounter_types()
1843 msg = _(
1844 'Select the default type for new encounters.\n'
1845 '\n'
1846 'Leaving this unset will make GNUmed apply the most commonly used type.\n'
1847 )
1848 gmCfgWidgets.configure_string_from_list_option (
1849 parent = self,
1850 message = msg,
1851 option = 'encounter.default_type',
1852 bias = 'user',
1853
1854 choices = [ e[0] for e in enc_types ],
1855 columns = [_('Encounter type')],
1856 data = [ e[1] for e in enc_types ]
1857 )
1858
1860 gmCfgWidgets.configure_boolean_option (
1861 parent = self,
1862 question = _(
1863 'Do you want GNUmed to show the encounter\n'
1864 'details editor when changing the active patient ?'
1865 ),
1866 option = 'encounter.show_editor_before_patient_change',
1867 button_tooltips = [
1868 _('Yes, show the encounter editor if it seems appropriate.'),
1869 _('No, never show the encounter editor even if it would seem useful.')
1870 ]
1871 )
1872
1877
1878 gmCfgWidgets.configure_string_option (
1879 message = _(
1880 'When a patient is activated GNUmed checks the\n'
1881 'chart for encounters lacking any entries.\n'
1882 '\n'
1883 'Any such encounters older than what you set\n'
1884 'here will be removed from the medical record.\n'
1885 '\n'
1886 'To effectively disable removal of such encounters\n'
1887 'set this option to an improbable value.\n'
1888 ),
1889 option = 'encounter.ttl_if_empty',
1890 bias = 'user',
1891 default_value = '1 week',
1892 validator = is_valid
1893 )
1894
1899
1900 gmCfgWidgets.configure_string_option (
1901 message = _(
1902 'When a patient is activated GNUmed checks the\n'
1903 'age of the most recent encounter.\n'
1904 '\n'
1905 'If that encounter is younger than this age\n'
1906 'the existing encounter will be continued.\n'
1907 '\n'
1908 '(If it is really old a new encounter is\n'
1909 ' started, or else GNUmed will ask you.)\n'
1910 ),
1911 option = 'encounter.minimum_ttl',
1912 bias = 'user',
1913 default_value = '1 hour 30 minutes',
1914 validator = is_valid
1915 )
1916
1921
1922 gmCfgWidgets.configure_string_option (
1923 message = _(
1924 'When a patient is activated GNUmed checks the\n'
1925 'age of the most recent encounter.\n'
1926 '\n'
1927 'If that encounter is older than this age\n'
1928 'GNUmed will always start a new encounter.\n'
1929 '\n'
1930 '(If it is very recent the existing encounter\n'
1931 ' is continued, or else GNUmed will ask you.)\n'
1932 ),
1933 option = 'encounter.maximum_ttl',
1934 bias = 'user',
1935 default_value = '6 hours',
1936 validator = is_valid
1937 )
1938
1947
1948 gmCfgWidgets.configure_string_option (
1949 message = _(
1950 'At any time there can only be one open (ongoing)\n'
1951 'episode for each health issue.\n'
1952 '\n'
1953 'When you try to open (add data to) an episode on a health\n'
1954 'issue GNUmed will check for an existing open episode on\n'
1955 'that issue. If there is any it will check the age of that\n'
1956 'episode. The episode is closed if it has been dormant (no\n'
1957 'data added, that is) for the period of time (in days) you\n'
1958 'set here.\n'
1959 '\n'
1960 "If the existing episode hasn't been dormant long enough\n"
1961 'GNUmed will consult you what to do.\n'
1962 '\n'
1963 'Enter maximum episode dormancy in DAYS:'
1964 ),
1965 option = 'episode.ttl',
1966 bias = 'user',
1967 default_value = 60,
1968 validator = is_valid
1969 )
1970
2001
2016
2041
2053
2054 gmCfgWidgets.configure_string_option (
2055 message = _(
2056 'GNUmed can check for new releases being available. To do\n'
2057 'so it needs to load version information from an URL.\n'
2058 '\n'
2059 'The default URL is:\n'
2060 '\n'
2061 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
2062 '\n'
2063 'but you can configure any other URL locally. Note\n'
2064 'that you must enter the location as a valid URL.\n'
2065 'Depending on the URL the client will need online\n'
2066 'access when checking for updates.'
2067 ),
2068 option = u'horstspace.update.url',
2069 bias = u'workplace',
2070 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
2071 validator = is_valid
2072 )
2073
2091
2108
2125
2136
2137 gmCfgWidgets.configure_string_option (
2138 message = _(
2139 'GNUmed can show the document review dialog after\n'
2140 'calling the appropriate viewer for that document.\n'
2141 '\n'
2142 'Select the conditions under which you want\n'
2143 'GNUmed to do so:\n'
2144 '\n'
2145 ' 0: never display the review dialog\n'
2146 ' 1: always display the dialog\n'
2147 ' 2: only if there is no previous review by me\n'
2148 ' 3: only if there is no previous review at all\n'
2149 ' 4: only if there is no review by the responsible reviewer\n'
2150 '\n'
2151 'Note that if a viewer is configured to not block\n'
2152 'GNUmed during document display the review dialog\n'
2153 'will actually appear in parallel to the viewer.'
2154 ),
2155 option = u'horstspace.document_viewer.review_after_display',
2156 bias = u'user',
2157 default_value = 3,
2158 validator = is_valid
2159 )
2160
2162
2163
2164 master_data_lists = [
2165 'adr',
2166 'billables',
2167 'drugs',
2168 'hints',
2169 'codes',
2170 'communication_channel_types',
2171 'substances_in_brands',
2172 'substances',
2173 'labs',
2174 'form_templates',
2175 'doc_types',
2176 'enc_types',
2177 'text_expansions',
2178 'meta_test_types',
2179 'orgs',
2180 'patient_tags',
2181 'provinces',
2182 'db_translations',
2183 'ref_data_sources',
2184 'test_types',
2185 'test_panels',
2186 'vacc_indications',
2187 'vaccines',
2188 'workplaces'
2189 ]
2190
2191 master_data_list_names = {
2192 'adr': _('Addresses (likely slow)'),
2193 'drugs': _('Branded drugs (as marketed)'),
2194 'hints': _('Clinical hints'),
2195 'codes': _('Codes and their respective terms'),
2196 'communication_channel_types': _('Communication channel types'),
2197 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2198 'labs': _('Diagnostic organizations (path labs, ...)'),
2199 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2200 'doc_types': _('Document types'),
2201 'enc_types': _('Encounter types'),
2202 'text_expansions': _('Keyword based text expansion macros'),
2203 'meta_test_types': _('Meta test/measurement types'),
2204 'orgs': _('Organizations with their units, addresses, and comm channels'),
2205 'patient_tags': _('Patient tags'),
2206 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2207 'db_translations': _('String translations in the database'),
2208 'test_types': _('Test/measurement types'),
2209 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2210 'vaccines': _('Vaccines'),
2211 'workplaces': _('Workplace profiles (which plugins to load)'),
2212 'substances': _('Consumable substances'),
2213 'billables': _('Billable items'),
2214 'ref_data_sources': _('Reference data sources'),
2215 'test_panels': _('Test/measurement panels/profiles')
2216 }
2217
2218 map_list2handler = {
2219 'form_templates': gmFormWidgets.manage_form_templates,
2220 'doc_types': gmDocumentWidgets.manage_document_types,
2221 'text_expansions': gmKeywordExpansionWidgets.configure_keyword_text_expansion,
2222 'db_translations': gmI18nWidgets.manage_translations,
2223 'codes': gmCodingWidgets.browse_coded_terms,
2224 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2225 'provinces': gmAddressWidgets.manage_provinces,
2226 'workplaces': gmPraxisWidgets.configure_workplace_plugins,
2227 'drugs': gmMedicationWidgets.manage_branded_drugs,
2228 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2229 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2230 'test_types': gmMeasurementWidgets.manage_measurement_types,
2231 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2232 'vaccines': gmVaccWidgets.manage_vaccines,
2233 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2234 'orgs': gmOrganizationWidgets.manage_orgs,
2235 'adr': gmAddressWidgets.manage_addresses,
2236 'substances': gmMedicationWidgets.manage_consumable_substances,
2237 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2238 'communication_channel_types': gmContactWidgets.manage_comm_channel_types,
2239 'billables': gmBillingWidgets.manage_billables,
2240 'ref_data_sources': gmCodingWidgets.browse_data_sources,
2241 'hints': gmProviderInboxWidgets.browse_dynamic_hints,
2242 'test_panels': gmMeasurementWidgets.manage_test_panels
2243 }
2244
2245
2246 def edit(item):
2247 try: map_list2handler[item](parent = self)
2248 except KeyError: pass
2249 return False
2250
2251
2252 gmListWidgets.get_choices_from_list (
2253 parent = self,
2254 caption = _('Master data management'),
2255 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2256 data = master_data_lists,
2257 columns = [_('Select the list you want to manage:')],
2258 edit_callback = edit,
2259 single_selection = True,
2260 ignore_OK_button = True
2261 )
2262
2265
2267
2268 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2269 if found:
2270 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2271 return
2272
2273 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2274 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking = False)
2275 return
2276
2277 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2278 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2279 if found:
2280 gmShellAPI.run_command_in_shell(cmd, blocking = False)
2281 return
2282
2283 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2284
2286
2287 curr_pat = gmPerson.gmCurrentPatient()
2288
2289 arriba = gmArriba.cArriba()
2290 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2291 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2292 return
2293
2294
2295 if curr_pat is None:
2296 return
2297
2298 if arriba.pdf_result is None:
2299 return
2300
2301 doc = gmDocumentWidgets.save_file_as_new_document (
2302 parent = self,
2303 filename = arriba.pdf_result,
2304 document_type = _('risk assessment')
2305 )
2306
2307 try: os.remove(arriba.pdf_result)
2308 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2309
2310 if doc is None:
2311 return
2312
2313 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2314 doc.save()
2315
2316 try:
2317 open(arriba.xml_result).close()
2318 part = doc.add_part(file = arriba.xml_result)
2319 except StandardError:
2320 _log.exception('error accessing [%s]', arriba.xml_result)
2321 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2322
2323 if part is None:
2324 return
2325
2326 part['obj_comment'] = u'XML-Daten'
2327 part['filename'] = u'arriba-result.xml'
2328 part.save()
2329
2331
2332 dbcfg = gmCfg.cCfgSQL()
2333 cmd = dbcfg.get2 (
2334 option = u'external.tools.acs_risk_calculator_cmd',
2335 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2336 bias = 'user'
2337 )
2338
2339 if cmd is None:
2340 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2341 return
2342
2343 cwd = os.path.expanduser(os.path.join('~', '.gnumed'))
2344 try:
2345 subprocess.check_call (
2346 args = (cmd,),
2347 close_fds = True,
2348 cwd = cwd
2349 )
2350 except (OSError, ValueError, subprocess.CalledProcessError):
2351 _log.exception('there was a problem executing [%s]', cmd)
2352 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2353 return
2354
2355 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2356 for pdf in pdfs:
2357 try:
2358 open(pdf).close()
2359 except:
2360 _log.exception('error accessing [%s]', pdf)
2361 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2362 continue
2363
2364 doc = gmDocumentWidgets.save_file_as_new_document (
2365 parent = self,
2366 filename = pdf,
2367 document_type = u'risk assessment'
2368 )
2369
2370 try:
2371 os.remove(pdf)
2372 except StandardError:
2373 _log.exception('cannot remove [%s]', pdf)
2374
2375 if doc is None:
2376 continue
2377 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2378 doc.save()
2379
2380 return
2381
2384
2387
2390
2392 dlg = gmSnellen.cSnellenCfgDlg()
2393 if dlg.ShowModal() != wx.ID_OK:
2394 return
2395
2396 frame = gmSnellen.cSnellenChart (
2397 width = dlg.vals[0],
2398 height = dlg.vals[1],
2399 alpha = dlg.vals[2],
2400 mirr = dlg.vals[3],
2401 parent = None
2402 )
2403 frame.CentreOnScreen(wx.BOTH)
2404
2405
2406 frame.Show(True)
2407
2408
2411
2414
2417
2418
2419
2423
2427
2431
2432
2433
2435 wx.CallAfter(self.__save_screenshot)
2436 evt.Skip()
2437
2439
2440 time.sleep(0.5)
2441
2442 rect = self.GetRect()
2443
2444
2445 if sys.platform == 'linux2':
2446 client_x, client_y = self.ClientToScreen((0, 0))
2447 border_width = client_x - rect.x
2448 title_bar_height = client_y - rect.y
2449
2450 if self.GetMenuBar():
2451 title_bar_height /= 2
2452 rect.width += (border_width * 2)
2453 rect.height += title_bar_height + border_width
2454
2455 wdc = wx.ScreenDC()
2456 mdc = wx.MemoryDC()
2457 img = wx.EmptyBitmap(rect.width, rect.height)
2458 mdc.SelectObject(img)
2459 mdc.Blit (
2460 0, 0,
2461 rect.width, rect.height,
2462 wdc,
2463 rect.x, rect.y
2464 )
2465
2466
2467 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2468 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2469 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2470
2472
2473 raise ValueError('raised ValueError to test exception handling')
2474
2476 raise gmExceptions.AccessDenied (
2477 _('[-9999]: <access violation test error>'),
2478 source = u'GNUmed code',
2479 code = -9999,
2480 details = _('This is a deliberate AcessDenied exception thrown to test the handling of access violations by means of a decorator.')
2481 )
2482
2483 @gmAccessPermissionWidgets.verify_minimum_required_role('admin', activity = _('testing access check for non-existant <admin> role'))
2485 raise gmExceptions.AccessDenied (
2486 _('[-9999]: <access violation test error>'),
2487 source = u'GNUmed code',
2488 code = -9999,
2489 details = _('This is a deliberate AcessDenied exception. You should not see this message because the role is checked in a decorator.')
2490 )
2491
2493 import wx.lib.inspection
2494 wx.lib.inspection.InspectionTool().Show()
2495
2498
2501
2504
2507
2514
2518
2521
2524
2531
2536
2538 name = os.path.basename(gmLog2._logfile_name)
2539 name, ext = os.path.splitext(name)
2540 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2541 new_path = os.path.expanduser(os.path.join('~', 'gnumed'))
2542
2543 dlg = wx.FileDialog (
2544 parent = self,
2545 message = _("Save current log as..."),
2546 defaultDir = new_path,
2547 defaultFile = new_name,
2548 wildcard = "%s (*.log)|*.log" % _("log files"),
2549 style = wx.SAVE
2550 )
2551 choice = dlg.ShowModal()
2552 new_name = dlg.GetPath()
2553 dlg.Destroy()
2554 if choice != wx.ID_OK:
2555 return True
2556
2557 _log.warning('syncing log file for backup to [%s]', new_name)
2558 gmLog2.flush()
2559 shutil.copy2(gmLog2._logfile_name, new_name)
2560 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2561
2564
2565
2566
2568 """This is the wx.EVT_CLOSE handler.
2569
2570 - framework still functional
2571 """
2572 _log.debug('gmTopLevelFrame.OnClose() start')
2573 self._clean_exit()
2574 self.Destroy()
2575 _log.debug('gmTopLevelFrame.OnClose() end')
2576 return True
2577
2583
2588
2596
2603
2610
2617
2627
2635
2643
2651
2659
2660 @gmAccessPermissionWidgets.verify_minimum_required_role('doctor', activity = _('manage vaccinations'))
2669
2678
2685
2702
2705
2708
2710
2711 pat = gmPerson.gmCurrentPatient()
2712 if not pat.connected:
2713 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2714 return False
2715
2716 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2717 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', pat['dirname']))
2718 gmTools.mkdir(aDefDir)
2719
2720 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2721 dlg = wx.FileDialog (
2722 parent = self,
2723 message = _("Save patient's EMR journal as..."),
2724 defaultDir = aDefDir,
2725 defaultFile = fname,
2726 wildcard = aWildcard,
2727 style = wx.SAVE
2728 )
2729 choice = dlg.ShowModal()
2730 fname = dlg.GetPath()
2731 dlg.Destroy()
2732 if choice != wx.ID_OK:
2733 return True
2734
2735 _log.debug('exporting EMR journal to [%s]' % fname)
2736
2737 exporter = gmPatientExporter.cEMRJournalExporter()
2738
2739 wx.BeginBusyCursor()
2740 try:
2741 fname = exporter.export_to_file(filename = fname)
2742 except:
2743 wx.EndBusyCursor()
2744 gmGuiHelpers.gm_show_error (
2745 _('Error exporting patient EMR as chronological journal.'),
2746 _('EMR journal export')
2747 )
2748 raise
2749 wx.EndBusyCursor()
2750
2751 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2752
2753 return True
2754
2761
2763 curr_pat = gmPerson.gmCurrentPatient()
2764 if not curr_pat.connected:
2765 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2766 return
2767
2768 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2769 if tag is None:
2770 return
2771
2772 tag = curr_pat.add_tag(tag['pk_tag_image'])
2773 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2774 comment = wx.GetTextFromUser (
2775 message = msg,
2776 caption = _('Editing tag comment'),
2777 default_value = gmTools.coalesce(tag['comment'], u''),
2778 parent = self
2779 )
2780
2781 if comment == u'':
2782 return
2783
2784 if comment.strip() == tag['comment']:
2785 return
2786
2787 if comment == u' ':
2788 tag['comment'] = None
2789 else:
2790 tag['comment'] = comment.strip()
2791
2792 tag.save()
2793
2803
2805 curr_pat = gmPerson.gmCurrentPatient()
2806 if not curr_pat.connected:
2807 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2808 return False
2809 enc = 'cp850'
2810 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'current-patient.gdt'))
2811 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2812 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2813
2816
2819
2827
2835
2838
2845
2849
2852
2855
2858
2861
2866
2868 """Cleanup helper.
2869
2870 - should ALWAYS be called when this program is
2871 to be terminated
2872 - ANY code that should be executed before a
2873 regular shutdown should go in here
2874 - framework still functional
2875 """
2876 _log.debug('gmTopLevelFrame._clean_exit() start')
2877
2878
2879 listener = gmBackendListener.gmBackendListener()
2880 try:
2881 listener.shutdown()
2882 except:
2883 _log.exception('cannot stop backend notifications listener thread')
2884
2885
2886 if _scripting_listener is not None:
2887 try:
2888 _scripting_listener.shutdown()
2889 except:
2890 _log.exception('cannot stop scripting listener thread')
2891
2892
2893 self.clock_update_timer.Stop()
2894 gmTimer.shutdown()
2895 gmPhraseWheel.shutdown()
2896
2897
2898 for call_back in self.__pre_exit_callbacks:
2899 try:
2900 call_back()
2901 except:
2902 print "*** pre-exit callback failed ***"
2903 print call_back
2904 _log.exception('callback [%s] failed', call_back)
2905
2906
2907 gmDispatcher.send(u'application_closing')
2908
2909
2910 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2911
2912
2913 curr_width, curr_height = self.GetClientSizeTuple()
2914 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2915 dbcfg = gmCfg.cCfgSQL()
2916 dbcfg.set (
2917 option = 'main.window.width',
2918 value = curr_width,
2919 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
2920 )
2921 dbcfg.set (
2922 option = 'main.window.height',
2923 value = curr_height,
2924 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
2925 )
2926
2927 if _cfg.get(option = 'debug'):
2928 print '---=== GNUmed shutdown ===---'
2929 try:
2930 print _('You have to manually close this window to finalize shutting down GNUmed.')
2931 print _('This is so that you can inspect the console output at your leisure.')
2932 except UnicodeEncodeError:
2933 print 'You have to manually close this window to finalize shutting down GNUmed.'
2934 print 'This is so that you can inspect the console output at your leisure.'
2935 print '---=== GNUmed shutdown ===---'
2936
2937
2938 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2939
2940
2941 import threading
2942 _log.debug("%s active threads", threading.activeCount())
2943 for t in threading.enumerate():
2944 _log.debug('thread %s', t)
2945
2946 _log.debug('gmTopLevelFrame._clean_exit() end')
2947
2948
2949
2951
2952 if _cfg.get(option = 'slave'):
2953 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s in %%(site)s of %%(prax)s] (%s:%s)' % (
2954 _cfg.get(option = 'slave personality'),
2955 _cfg.get(option = 'xml-rpc port')
2956 )
2957 else:
2958 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s in %(site)s of %(prax)s]'
2959
2961 """Update title of main window based on template.
2962
2963 This gives nice tooltips on iconified GNUmed instances.
2964
2965 User research indicates that in the title bar people want
2966 the date of birth, not the age, so please stick to this
2967 convention.
2968 """
2969 args = {}
2970
2971 pat = gmPerson.gmCurrentPatient()
2972 if pat.connected:
2973 args['pat'] = u'%s %s %s (%s) #%d' % (
2974 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2975 pat['firstnames'],
2976 pat['lastnames'],
2977 pat.get_formatted_dob(format = '%Y %b %d', encoding = gmI18N.get_encoding()),
2978 pat['pk_identity']
2979 )
2980 else:
2981 args['pat'] = _('no patient')
2982
2983 args['prov'] = u'%s%s.%s' % (
2984 gmTools.coalesce(_provider['title'], u'', u'%s '),
2985 _provider['firstnames'][:1],
2986 _provider['lastnames']
2987 )
2988
2989 praxis = gmPraxis.gmCurrentPraxisBranch()
2990 args['wp'] = praxis.active_workplace
2991 args['site'] = praxis['branch']
2992 args['prax'] = praxis['praxis']
2993
2994 self.SetTitle(self.__title_template % args)
2995
2996
2998 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2999 sb.SetStatusWidths([-1, 225])
3000
3001 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
3002 self._cb_update_clock()
3003
3004 self.clock_update_timer.Start(milliseconds = 1000)
3005
3007 """Displays date and local time in the second slot of the status bar"""
3008 t = time.localtime(time.time())
3009 st = time.strftime('%Y %b %d %H:%M:%S', t).decode(gmI18N.get_encoding(), 'replace')
3010 self.SetStatusText(st, 1)
3011
3013 """Lock GNUmed client against unauthorized access"""
3014
3015
3016
3017 return
3018
3020 """Unlock the main notebook widgets
3021 As long as we are not logged into the database backend,
3022 all pages but the 'login' page of the main notebook widget
3023 are locked; i.e. not accessible by the user
3024 """
3025
3026
3027
3028
3029
3030 return
3031
3033 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
3034
3036
3038
3039 self.__starting_up = True
3040
3041 gmExceptionHandlingWidgets.install_wx_exception_handler()
3042 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
3043
3044
3045 self.SetAppName(u'gnumed')
3046 self.SetVendorName(u'The GNUmed Development Community.')
3047 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3048 paths.init_paths(wx = wx, app_name = u'gnumed')
3049
3050 if not self.__setup_prefs_file():
3051 return False
3052
3053 gmExceptionHandlingWidgets.set_sender_email(gmPraxis.gmCurrentPraxisBranch().user_email)
3054
3055 self.__guibroker = gmGuiBroker.GuiBroker()
3056 self.__setup_platform()
3057
3058 if not self.__establish_backend_connection():
3059 return False
3060 if not self.__verify_db_account():
3061 return False
3062 if not self.__verify_praxis_branch():
3063 return False
3064
3065 self.__check_db_lang()
3066 self.__update_workplace_list()
3067
3068 if not _cfg.get(option = 'skip-update-check'):
3069 self.__check_for_updates()
3070
3071 if _cfg.get(option = 'slave'):
3072 if not self.__setup_scripting_listener():
3073 return False
3074
3075
3076 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
3077 frame.CentreOnScreen(wx.BOTH)
3078 self.SetTopWindow(frame)
3079 frame.Show(True)
3080
3081 if _cfg.get(option = 'debug'):
3082 self.RedirectStdio()
3083 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
3084
3085
3086 print '---=== GNUmed startup ===---'
3087 print _('redirecting STDOUT/STDERR to this log window')
3088 print '---=== GNUmed startup ===---'
3089
3090 self.__setup_user_activity_timer()
3091 self.__register_events()
3092
3093 wx.CallAfter(self._do_after_init)
3094
3095 return True
3096
3098 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
3099
3100 - after destroying all application windows and controls
3101 - before wx.Windows internal cleanup
3102 """
3103 _log.debug('gmApp.OnExit() start')
3104
3105 self.__shutdown_user_activity_timer()
3106
3107 if _cfg.get(option = 'debug'):
3108 self.RestoreStdio()
3109 sys.stdin = sys.__stdin__
3110 sys.stdout = sys.__stdout__
3111 sys.stderr = sys.__stderr__
3112
3113 _log.debug('gmApp.OnExit() end')
3114
3116 wx.Bell()
3117 wx.Bell()
3118 wx.Bell()
3119 _log.warning('unhandled event detected: QUERY_END_SESSION')
3120 _log.info('we should be saving ourselves from here')
3121 gmLog2.flush()
3122 print "unhandled event detected: QUERY_END_SESSION"
3123
3125 wx.Bell()
3126 wx.Bell()
3127 wx.Bell()
3128 _log.warning('unhandled event detected: END_SESSION')
3129 gmLog2.flush()
3130 print "unhandled event detected: END_SESSION"
3131
3142
3144 self.user_activity_detected = True
3145 evt.Skip()
3146
3148
3149 if self.user_activity_detected:
3150 self.elapsed_inactivity_slices = 0
3151 self.user_activity_detected = False
3152 self.elapsed_inactivity_slices += 1
3153 else:
3154 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
3155
3156 pass
3157
3158 self.user_activity_timer.Start(oneShot = True)
3159
3160
3161
3163 try:
3164 kwargs['originated_in_database']
3165 print '==> got notification from database "%s":' % kwargs['signal']
3166 except KeyError:
3167 print '==> received signal from client: "%s"' % kwargs['signal']
3168
3169 del kwargs['signal']
3170 for key in kwargs.keys():
3171 print ' [%s]: %s' % (key, kwargs[key])
3172
3179
3181 self.user_activity_detected = True
3182 self.elapsed_inactivity_slices = 0
3183
3184 self.max_user_inactivity_slices = 15
3185 self.user_activity_timer = gmTimer.cTimer (
3186 callback = self._on_user_activity_timer_expired,
3187 delay = 2000
3188 )
3189 self.user_activity_timer.Start(oneShot=True)
3190
3192 try:
3193 self.user_activity_timer.Stop()
3194 del self.user_activity_timer
3195 except:
3196 pass
3197
3199 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
3200 wx.EVT_END_SESSION(self, self._on_end_session)
3201
3202
3203
3204
3205
3206 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
3207
3208 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
3209 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
3210
3211 if _cfg.get(option = 'debug'):
3212 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
3213 _log.debug('connected signal monitor')
3214
3230
3243
3272
3274
3275 if not gmPraxisWidgets.set_active_praxis_branch(no_parent = True):
3276 return False
3277
3278 login = gmPG2.get_default_login()
3279 msg = u'\n'
3280 msg += _('Database <%s> on <%s>') % (
3281 login.database,
3282 gmTools.coalesce(login.host, u'localhost')
3283 )
3284 msg += u'\n\n'
3285
3286 praxis = gmPraxis.gmCurrentPraxisBranch()
3287 msg += _('Branch "%s" of praxis "%s"\n') % (
3288 praxis['branch'],
3289 praxis['praxis']
3290 )
3291 msg += u'\n\n'
3292
3293 banner = praxis.db_logon_banner
3294 if banner.strip() == u'':
3295 return True
3296 msg += banner
3297 msg += u'\n\n'
3298
3299 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3300 None,
3301 -1,
3302 caption = _('Verifying database'),
3303 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3304 button_defs = [
3305 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3306 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3307 ]
3308 )
3309 log_on = dlg.ShowModal()
3310 dlg.Destroy()
3311 if log_on == wx.ID_YES:
3312 return True
3313 _log.info('user decided to not connect to this database')
3314 return False
3315
3329
3331 """Setup access to a config file for storing preferences."""
3332
3333 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3334
3335 candidates = []
3336 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3337 if explicit_file is not None:
3338 candidates.append(explicit_file)
3339
3340 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3341 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3342 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3343
3344 prefs_file = None
3345 for candidate in candidates:
3346 try:
3347 open(candidate, 'a+').close()
3348 prefs_file = candidate
3349 break
3350 except IOError:
3351 continue
3352
3353 if prefs_file is None:
3354 msg = _(
3355 'Cannot find configuration file in any of:\n'
3356 '\n'
3357 ' %s\n'
3358 'You may need to use the comand line option\n'
3359 '\n'
3360 ' --conf-file=<FILE>'
3361 ) % '\n '.join(candidates)
3362 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3363 return False
3364
3365 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3366 _log.info('user preferences file: %s', prefs_file)
3367
3368 return True
3369
3371
3372 from socket import error as SocketError
3373 from Gnumed.pycommon import gmScriptingListener
3374 from Gnumed.wxpython import gmMacro
3375
3376 slave_personality = gmTools.coalesce (
3377 _cfg.get (
3378 group = u'workplace',
3379 option = u'slave personality',
3380 source_order = [
3381 ('explicit', 'return'),
3382 ('workbase', 'return'),
3383 ('user', 'return'),
3384 ('system', 'return')
3385 ]
3386 ),
3387 u'gnumed-client'
3388 )
3389 _cfg.set_option(option = 'slave personality', value = slave_personality)
3390
3391
3392 port = int (
3393 gmTools.coalesce (
3394 _cfg.get (
3395 group = u'workplace',
3396 option = u'xml-rpc port',
3397 source_order = [
3398 ('explicit', 'return'),
3399 ('workbase', 'return'),
3400 ('user', 'return'),
3401 ('system', 'return')
3402 ]
3403 ),
3404 9999
3405 )
3406 )
3407 _cfg.set_option(option = 'xml-rpc port', value = port)
3408
3409 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3410 global _scripting_listener
3411 try:
3412 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3413 except SocketError, e:
3414 _log.exception('cannot start GNUmed XML-RPC server')
3415 gmGuiHelpers.gm_show_error (
3416 aMessage = (
3417 'Cannot start the GNUmed server:\n'
3418 '\n'
3419 ' [%s]'
3420 ) % e,
3421 aTitle = _('GNUmed startup')
3422 )
3423 return False
3424
3425 return True
3426
3447
3449 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3450 _log.warning("system locale is undefined (probably meaning 'C')")
3451 return True
3452
3453 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3454 curr_db_lang = rows[0]['lang']
3455 _log.debug("current database locale: [%s]" % curr_db_lang)
3456
3457 if curr_db_lang is None:
3458
3459 cmd = u'select i18n.set_curr_lang(%s)'
3460 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3461 if len(lang) == 0:
3462 continue
3463 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
3464 if rows[0][0]:
3465 _log.debug("Successfully set database language to [%s]." % lang)
3466 return True
3467 _log.error('Cannot set database language to [%s].' % lang)
3468
3469 return True
3470
3471 if curr_db_lang == gmI18N.system_locale_level['full']:
3472 _log.debug('Database locale (%s) up to date.' % curr_db_lang)
3473 return True
3474 if curr_db_lang == gmI18N.system_locale_level['country']:
3475 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (curr_db_lang, gmI18N.system_locale))
3476 return True
3477 if curr_db_lang == gmI18N.system_locale_level['language']:
3478 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (curr_db_lang, gmI18N.system_locale))
3479 return True
3480
3481 _log.warning('database locale [%s] does not match system locale [%s]' % (curr_db_lang, gmI18N.system_locale))
3482
3483 sys_lang2ignore = _cfg.get (
3484 group = u'backend',
3485 option = u'ignored mismatching system locale',
3486 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3487 )
3488 if gmI18N.system_locale == sys_lang2ignore:
3489 _log.info('configured to ignore system-to-database locale mismatch')
3490 return True
3491
3492
3493 msg = _(
3494 "The currently selected database language ('%s') does\n"
3495 "not match the current system language ('%s').\n"
3496 "\n"
3497 "Do you want to set the database language to '%s' ?\n"
3498 ) % (curr_db_lang, gmI18N.system_locale, gmI18N.system_locale)
3499 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3500 None,
3501 -1,
3502 caption = _('Checking database language settings'),
3503 question = msg,
3504 button_defs = [
3505 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3506 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3507 ],
3508 show_checkbox = True,
3509 checkbox_msg = _('Remember to ignore language mismatch'),
3510 checkbox_tooltip = _(
3511 'Checking this will make GNUmed remember your decision\n'
3512 'until the system language is changed.\n'
3513 '\n'
3514 'You can also reactivate this inquiry by removing the\n'
3515 'corresponding "ignore" option from the configuration file\n'
3516 '\n'
3517 ' [%s]'
3518 ) % _cfg.get(option = 'user_preferences_file')
3519 )
3520 decision = dlg.ShowModal()
3521 remember2ignore_this_mismatch = dlg._CHBOX_dont_ask_again.GetValue()
3522 dlg.Destroy()
3523
3524 if decision == wx.ID_NO:
3525 if not remember2ignore_this_mismatch:
3526 return True
3527 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3528 gmCfg2.set_option_in_INI_file (
3529 filename = _cfg.get(option = 'user_preferences_file'),
3530 group = 'backend',
3531 option = 'ignored mismatching system locale',
3532 value = gmI18N.system_locale
3533 )
3534 return True
3535
3536
3537 cmd = u'select i18n.set_curr_lang(%s)'
3538 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3539 if len(lang) == 0:
3540 continue
3541 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
3542 if rows[0][0]:
3543 _log.debug("Successfully set database language to [%s]." % lang)
3544 return True
3545 _log.error('Cannot set database language to [%s].' % lang)
3546
3547
3548 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3549 gmPG2.run_rw_queries(queries = [{
3550 'cmd': u'select i18n.force_curr_lang(%s)',
3551 'args': [gmI18N.system_locale_level['country']]
3552 }])
3553
3554 return True
3555
3557 try:
3558 kwargs['originated_in_database']
3559 print '==> got notification from database "%s":' % kwargs['signal']
3560 except KeyError:
3561 print '==> received signal from client: "%s"' % kwargs['signal']
3562
3563 del kwargs['signal']
3564 for key in kwargs.keys():
3565
3566 try: print ' [%s]: %s' % (key, kwargs[key])
3567 except: print 'cannot print signal information'
3568
3572
3583
3585
3586 if _cfg.get(option = 'debug'):
3587 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3588 _log.debug('gmDispatcher signal monitor activated')
3589
3590 setup_safe_wxEndBusyCursor()
3591
3592 wx.InitAllImageHandlers()
3593
3594
3595
3596 app = gmApp(redirect = False, clearSigInt = False)
3597 app.MainLoop()
3598
3599
3600
3601 if __name__ == '__main__':
3602
3603 from GNUmed.pycommon import gmI18N
3604 gmI18N.activate_locale()
3605 gmI18N.install_domain()
3606
3607 _log.info('Starting up as main module.')
3608 main()
3609
3610
3611