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'), _('Add (a) measurement result(s) for the current patient.'))
635 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
636
637 item = menu_emr_edit.Append(-1, _('&Measurements'), _('Manage measurement results for the current patient.'))
638 self.Bind(wx.EVT_MENU, self.__on_manage_measurements, item)
639
640 item = menu_emr_edit.Append(-1, _('&Vaccinations'), _('Add (a) vaccination(s) for the current patient.'))
641 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
642
643 item = menu_emr_edit.Append(-1, _('&Family history (FHx)'), _('Manage family history.'))
644 self.Bind(wx.EVT_MENU, self.__on_manage_fhx, item)
645
646 item = menu_emr_edit.Append(-1, _('&Encounters'), _('List all encounters including empty ones.'))
647 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
648
649 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
650
651
652 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
653 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
654
655 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
656 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
657
658
659
660
661
662 item = menu_emr.Append(-1, _('Statistics'), _('Show a high-level statistic summary of the EMR.'))
663 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
664
665
666
667
668 menu_emr.AppendSeparator()
669
670
671 menu_emr_export = wx.Menu()
672
673 ID_EXPORT_EMR_ASCII = wx.NewId()
674 menu_emr_export.Append (
675 ID_EXPORT_EMR_ASCII,
676 _('Text document'),
677 _("Export the EMR of the active patient into a text file")
678 )
679 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
680
681 ID_EXPORT_EMR_JOURNAL = wx.NewId()
682 menu_emr_export.Append (
683 ID_EXPORT_EMR_JOURNAL,
684 _('Journal'),
685 _("Export the EMR of the active patient as a chronological journal into a text file")
686 )
687 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
688
689 ID_EXPORT_MEDISTAR = wx.NewId()
690 menu_emr_export.Append (
691 ID_EXPORT_MEDISTAR,
692 _('MEDISTAR import format'),
693 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
694 )
695 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
696
697 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
698
699 menu_emr.AppendSeparator()
700
701 self.mainmenu.Append(menu_emr, _("&EMR"))
702 self.__gb['main.emrmenu'] = menu_emr
703
704
705 menu_paperwork = wx.Menu()
706
707 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
708 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
709
710 menu_paperwork.AppendSeparator()
711
712 item = menu_paperwork.Append(-1, _('List Placeholders'), _('Show a list of all placeholders.'))
713 self.Bind(wx.EVT_MENU, self.__on_show_placeholders, item)
714
715 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
716
717
718 self.menu_tools = wx.Menu()
719
720 item = self.menu_tools.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
721 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
722
723 ID_DICOM_VIEWER = wx.NewId()
724 viewer = _('no viewer installed')
725 if gmShellAPI.detect_external_binary(binary = 'ginkgocadx')[0]:
726 viewer = u'Ginkgo CADx'
727 elif os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
728 viewer = u'OsiriX'
729 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
730 viewer = u'Aeskulap'
731 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
732 viewer = u'AMIDE'
733 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
734 viewer = u'DicomScope'
735 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
736 viewer = u'(x)medcon'
737 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)
738 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
739 if viewer == _('no viewer installed'):
740 _log.info('neither of Ginkgo CADx / OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
741 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
742
743
744
745
746
747 ID = wx.NewId()
748 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
749 wx.EVT_MENU(self, ID, self.__on_snellen)
750
751 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
752 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
753
754 ID_DICOM_VIEWER = wx.NewId()
755 self.menu_tools.Append(ID_DICOM_VIEWER, u'arriba', _('arriba: cardiovascular risk assessment (%s).') % u'www.arriba-hausarzt.de')
756 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_arriba)
757 if not gmShellAPI.detect_external_binary(binary = 'arriba')[0]:
758 _log.info('<arriba> not found, disabling "arriba" menu item')
759 self.menu_tools.Enable(id = ID_DICOM_VIEWER, enable = False)
760
761
762
763 item = self.menu_tools.Append(-1, u'HL7 (Exelleris)', 'Stage Excelleris HL7')
764 self.Bind(wx.EVT_MENU, self.__on_excelleris, item)
765
766 item = self.menu_tools.Append(-1, u'HL7', 'Stage generic HL7')
767 self.Bind(wx.EVT_MENU, self.__on_hl7, item)
768
769 item = self.menu_tools.Append(-1, u'Incoming', 'Browse incoming data')
770 self.Bind(wx.EVT_MENU, self.__on_incoming, item)
771
772 self.menu_tools.AppendSeparator()
773
774 self.mainmenu.Append(self.menu_tools, _("&Tools"))
775 self.__gb['main.toolsmenu'] = self.menu_tools
776
777
778 menu_knowledge = wx.Menu()
779
780
781 menu_drug_dbs = wx.Menu()
782
783 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
784 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
785
786
787
788
789
790
791 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
792
793 menu_id = wx.NewId()
794 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
795 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
796
797
798
799
800 ID_MEDICAL_LINKS = wx.NewId()
801 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
802 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
803
804 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
805 self.__gb['main.knowledgemenu'] = menu_knowledge
806
807
808 self.menu_office = wx.Menu()
809
810 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
811 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
812
813 self.menu_office.AppendSeparator()
814
815 item = self.menu_office.Append(-1, _('List bills'), _('List all bills across all patients.'))
816 self.Bind(wx.EVT_MENU, self.__on_show_all_bills, 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'name_mod_db', receiver = self._on_pat_name_changed)
927 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
928 gmDispatcher.connect(signal = u'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 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
932 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
933 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
934
935 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
936
937 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
938
939 _log.debug('registering plugin with menu system')
940 _log.debug(' generic name: %s', plugin_name)
941 _log.debug(' class name: %s', class_name)
942 _log.debug(' specific menu: %s', menu_name)
943 _log.debug(' menu item: %s', menu_item_name)
944
945
946 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
947 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
948 self.menu_id2plugin[item.Id] = class_name
949
950
951 if menu_name is not None:
952 menu = self.__gb['main.%smenu' % menu_name]
953 item = menu.Append(-1, menu_item_name, menu_help_string)
954 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
955 self.menu_id2plugin[item.Id] = class_name
956
957 return True
958
960 gmDispatcher.send (
961 signal = u'display_widget',
962 name = self.menu_id2plugin[evt.Id]
963 )
964
966 wx.Bell()
967 wx.Bell()
968 wx.Bell()
969 _log.warning('unhandled event detected: QUERY_END_SESSION')
970 _log.info('we should be saving ourselves from here')
971 gmLog2.flush()
972 print "unhandled event detected: QUERY_END_SESSION"
973
975 wx.Bell()
976 wx.Bell()
977 wx.Bell()
978 _log.warning('unhandled event detected: END_SESSION')
979 gmLog2.flush()
980 print "unhandled event detected: END_SESSION"
981
983 if not callable(callback):
984 raise TypeError(u'callback [%s] not callable' % callback)
985
986 self.__pre_exit_callbacks.append(callback)
987
988 - def _on_set_statustext_pubsub(self, context=None):
989 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
990 wx.CallAfter(self.SetStatusText, msg)
991
992 try:
993 if context.data['beep']:
994 wx.Bell()
995 except KeyError:
996 pass
997
998 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
999
1000 if msg is None:
1001 msg = _('programmer forgot to specify status message')
1002
1003 if loglevel is not None:
1004 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
1005
1006 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
1007 wx.CallAfter(self.SetStatusText, msg)
1008
1009 if beep:
1010 wx.Bell()
1011
1013 wx.CallAfter(self.__on_db_maintenance_warning)
1014
1016
1017 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
1018 wx.Bell()
1019 if not wx.GetApp().IsActive():
1020 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
1021
1022 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
1023
1024 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
1025 None,
1026 -1,
1027 caption = _('Database shutdown warning'),
1028 question = _(
1029 'The database will be shut down for maintenance\n'
1030 'in a few minutes.\n'
1031 '\n'
1032 'In order to not suffer any loss of data you\n'
1033 'will need to save your current work and log\n'
1034 'out of this GNUmed client.\n'
1035 ),
1036 button_defs = [
1037 {
1038 u'label': _('Close now'),
1039 u'tooltip': _('Close this GNUmed client immediately.'),
1040 u'default': False
1041 },
1042 {
1043 u'label': _('Finish work'),
1044 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
1045 u'default': True
1046 }
1047 ]
1048 )
1049 decision = dlg.ShowModal()
1050 if decision == wx.ID_YES:
1051 top_win = wx.GetApp().GetTopWindow()
1052 wx.CallAfter(top_win.Close)
1053
1055 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
1056
1058
1059 if not wx.GetApp().IsActive():
1060 if urgent:
1061 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
1062 else:
1063 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
1064
1065 if msg is not None:
1066 self.SetStatusText(msg)
1067
1068 if urgent:
1069 wx.Bell()
1070
1071 gmHooks.run_hook_script(hook = u'request_user_attention')
1072
1074 wx.CallAfter(self.__on_pat_name_changed)
1075
1077 self.__update_window_title()
1078
1079 - def _on_post_patient_selection(self, **kwargs):
1080 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
1081
1083 self.__update_window_title()
1084 gmDispatcher.send(signal = 'statustext', msg = u'')
1085 try:
1086 gmHooks.run_hook_script(hook = u'post_patient_activation')
1087 except:
1088 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
1089 raise
1090
1092 return self.__sanity_check_encounter()
1093
1095
1096
1097
1098 if _provider['role'] == u'secretary':
1099 return True
1100
1101 dbcfg = gmCfg.cCfgSQL()
1102 check_enc = bool(dbcfg.get2 (
1103 option = 'encounter.show_editor_before_patient_change',
1104 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1105 bias = 'user',
1106 default = True
1107 ))
1108
1109 if not check_enc:
1110 return True
1111
1112 pat = gmPerson.gmCurrentPatient()
1113 emr = pat.get_emr()
1114 enc = emr.active_encounter
1115
1116
1117 has_narr = enc.has_narrative()
1118 has_docs = enc.has_documents()
1119
1120 if (not has_narr) and (not has_docs):
1121 return True
1122
1123 empty_aoe = (gmTools.coalesce(enc['assessment_of_encounter'], '').strip() == u'')
1124 zero_duration = (enc['last_affirmed'] == enc['started'])
1125
1126
1127 if (not empty_aoe) and (not zero_duration):
1128 return True
1129
1130 if zero_duration:
1131 enc['last_affirmed'] = pyDT.datetime.now(tz=gmDateTime.gmCurrentLocalTimezone)
1132
1133
1134 if not has_narr:
1135 if empty_aoe:
1136 enc['assessment_of_encounter'] = _('only documents added')
1137 enc['pk_type'] = gmEMRStructItems.get_encounter_type(description = 'chart review')[0]['pk']
1138
1139 enc.save_payload()
1140 return True
1141
1142
1143 if empty_aoe:
1144
1145 epis = emr.get_episodes_by_encounter()
1146 if len(epis) > 0:
1147 enc_summary = ''
1148 for epi in epis:
1149 enc_summary += '%s; ' % epi['description']
1150 enc['assessment_of_encounter'] = enc_summary
1151
1152 msg = _('Edit the current encounter of the patient you are ABOUT TO LEAVE:')
1153 gmEMRStructWidgets.edit_encounter(parent = self, encounter = enc, msg = msg)
1154
1155 return True
1156
1157
1158
1161
1168
1172
1173
1174
1190
1219
1221 from Gnumed.wxpython import gmAbout
1222 contribs = gmAbout.cContributorsDlg (
1223 parent = self,
1224 id = -1,
1225 title = _('GNUmed contributors'),
1226 size = wx.Size(400,600),
1227 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1228 )
1229 contribs.ShowModal()
1230 del contribs
1231 del gmAbout
1232
1233
1234
1236 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1237 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1238 self.Close(True)
1239 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1240
1243
1245 send = gmGuiHelpers.gm_show_question (
1246 _('This will send a notification about database downtime\n'
1247 'to all GNUmed clients connected to your database.\n'
1248 '\n'
1249 'Do you want to send the notification ?\n'
1250 ),
1251 _('Announcing database maintenance downtime')
1252 )
1253 if not send:
1254 return
1255 gmPG2.send_maintenance_notification()
1256
1257
1260
1261
1262
1275
1276 gmCfgWidgets.configure_string_option (
1277 message = _(
1278 'Some network installations cannot cope with loading\n'
1279 'documents of arbitrary size in one piece from the\n'
1280 'database (mainly observed on older Windows versions)\n.'
1281 '\n'
1282 'Under such circumstances documents need to be retrieved\n'
1283 'in chunks and reassembled on the client.\n'
1284 '\n'
1285 'Here you can set the size (in Bytes) above which\n'
1286 'GNUmed will retrieve documents in chunks. Setting this\n'
1287 'value to 0 will disable the chunking protocol.'
1288 ),
1289 option = 'horstspace.blob_export_chunk_size',
1290 bias = 'workplace',
1291 default_value = 1024 * 1024,
1292 validator = is_valid
1293 )
1294
1295
1296
1364
1368
1369
1370
1379
1380 gmCfgWidgets.configure_string_option (
1381 message = _(
1382 'When GNUmed cannot find an OpenOffice server it\n'
1383 'will try to start one. OpenOffice, however, needs\n'
1384 'some time to fully start up.\n'
1385 '\n'
1386 'Here you can set the time for GNUmed to wait for OOo.\n'
1387 ),
1388 option = 'external.ooo.startup_settle_time',
1389 bias = 'workplace',
1390 default_value = 2.0,
1391 validator = is_valid
1392 )
1393
1396
1411
1412 gmCfgWidgets.configure_string_option (
1413 message = _(
1414 'GNUmed will use this URL to access a website which lets\n'
1415 'you report an adverse drug reaction (ADR).\n'
1416 '\n'
1417 'If you leave this empty it will fall back\n'
1418 'to an URL for reporting ADRs in Germany.'
1419 ),
1420 option = 'external.urls.report_ADR',
1421 bias = 'user',
1422 default_value = german_default,
1423 validator = is_valid
1424 )
1425
1439
1440 gmCfgWidgets.configure_string_option (
1441 message = _(
1442 'GNUmed will use this URL to access a website which lets\n'
1443 'you report an adverse vaccination reaction (vADR).\n'
1444 '\n'
1445 'If you set it to a specific address that URL must be\n'
1446 'accessible now. If you leave it empty it will fall back\n'
1447 'to the URL for reporting other adverse drug reactions.'
1448 ),
1449 option = 'external.urls.report_vaccine_ADR',
1450 bias = 'user',
1451 default_value = german_default,
1452 validator = is_valid
1453 )
1454
1468
1469 gmCfgWidgets.configure_string_option (
1470 message = _(
1471 'GNUmed will use this URL to access an encyclopedia of\n'
1472 'measurement/lab methods from within the measurments grid.\n'
1473 '\n'
1474 'You can leave this empty but to set it to a specific\n'
1475 'address the URL must be accessible now.'
1476 ),
1477 option = 'external.urls.measurements_encyclopedia',
1478 bias = 'user',
1479 default_value = german_default,
1480 validator = is_valid
1481 )
1482
1496
1497 gmCfgWidgets.configure_string_option (
1498 message = _(
1499 'GNUmed will use this URL to access a page showing\n'
1500 'vaccination schedules.\n'
1501 '\n'
1502 'You can leave this empty but to set it to a specific\n'
1503 'address the URL must be accessible now.'
1504 ),
1505 option = 'external.urls.vaccination_plans',
1506 bias = 'user',
1507 default_value = german_default,
1508 validator = is_valid
1509 )
1510
1523
1524 gmCfgWidgets.configure_string_option (
1525 message = _(
1526 'Enter the shell command with which to start the\n'
1527 'the ACS risk assessment calculator.\n'
1528 '\n'
1529 'GNUmed will try to verify the path which may,\n'
1530 'however, fail if you are using an emulator such\n'
1531 'as Wine. Nevertheless, starting the calculator\n'
1532 'will work as long as the shell command is correct\n'
1533 'despite the failing test.'
1534 ),
1535 option = 'external.tools.acs_risk_calculator_cmd',
1536 bias = 'user',
1537 validator = is_valid
1538 )
1539
1542
1555
1556 gmCfgWidgets.configure_string_option (
1557 message = _(
1558 'Enter the shell command with which to start\n'
1559 'the FreeDiams drug database frontend.\n'
1560 '\n'
1561 'GNUmed will try to verify that path.'
1562 ),
1563 option = 'external.tools.freediams_cmd',
1564 bias = 'workplace',
1565 default_value = None,
1566 validator = is_valid
1567 )
1568
1581
1582 gmCfgWidgets.configure_string_option (
1583 message = _(
1584 'Enter the shell command with which to start the\n'
1585 'the IFAP drug database.\n'
1586 '\n'
1587 'GNUmed will try to verify the path which may,\n'
1588 'however, fail if you are using an emulator such\n'
1589 'as Wine. Nevertheless, starting IFAP will work\n'
1590 'as long as the shell command is correct despite\n'
1591 'the failing test.'
1592 ),
1593 option = 'external.ifap-win.shell_command',
1594 bias = 'workplace',
1595 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1596 validator = is_valid
1597 )
1598
1599
1600
1649
1650
1651
1668
1671
1674
1679
1680 gmCfgWidgets.configure_string_option (
1681 message = _(
1682 'When a patient is activated GNUmed checks the\n'
1683 "proximity of the patient's birthday.\n"
1684 '\n'
1685 'If the birthday falls within the range of\n'
1686 ' "today %s <the interval you set here>"\n'
1687 'GNUmed will remind you of the recent or\n'
1688 'imminent anniversary.'
1689 ) % u'\u2213',
1690 option = u'patient_search.dob_warn_interval',
1691 bias = 'user',
1692 default_value = '1 week',
1693 validator = is_valid
1694 )
1695
1697
1698 gmCfgWidgets.configure_boolean_option (
1699 parent = self,
1700 question = _(
1701 'When adding progress notes do you want to\n'
1702 'allow opening several unassociated, new\n'
1703 'episodes for a patient at once ?\n'
1704 '\n'
1705 'This can be particularly helpful when entering\n'
1706 'progress notes on entirely new patients presenting\n'
1707 'with a multitude of problems on their first visit.'
1708 ),
1709 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1710 button_tooltips = [
1711 _('Yes, allow for multiple new episodes concurrently.'),
1712 _('No, only allow editing one new episode at a time.')
1713 ]
1714 )
1715
1717
1718 gmCfgWidgets.configure_boolean_option (
1719 parent = self,
1720 question = _(
1721 'When activating a patient, do you want GNUmed to\n'
1722 'auto-open editors for all active problems that were\n'
1723 'touched upon during the current and the most recent\n'
1724 'encounter ?'
1725 ),
1726 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1727 button_tooltips = [
1728 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1729 _('No, only auto-open one editor for a new, unassociated problem.')
1730 ]
1731 )
1732
1778
1779
1780
1783
1786
1799
1800 gmCfgWidgets.configure_string_option (
1801 message = _(
1802 'GNUmed will use this URL to let you browse\n'
1803 'billing catalogs (schedules of fees).\n'
1804 '\n'
1805 'You can leave this empty but to set it to a specific\n'
1806 'address the URL must be accessible now.'
1807 ),
1808 option = 'external.urls.schedules_of_fees',
1809 bias = 'user',
1810 default_value = german_default,
1811 validator = is_valid
1812 )
1813
1814
1815
1818
1821
1823 gmCfgWidgets.configure_string_from_list_option (
1824 parent = self,
1825 message = _('Select the default prescription mode.\n'),
1826 option = 'horst_space.default_prescription_mode',
1827 bias = 'user',
1828 default_value = u'form',
1829 choices = [ _('Formular'), _('Datenbank') ],
1830 columns = [_('Prescription mode')],
1831 data = [ u'form', u'database' ]
1832 )
1833
1836
1839
1841 enc_types = gmEMRStructItems.get_encounter_types()
1842 msg = _(
1843 'Select the default type for new encounters.\n'
1844 '\n'
1845 'Leaving this unset will make GNUmed apply the most commonly used type.\n'
1846 )
1847 gmCfgWidgets.configure_string_from_list_option (
1848 parent = self,
1849 message = msg,
1850 option = 'encounter.default_type',
1851 bias = 'user',
1852
1853 choices = [ e[0] for e in enc_types ],
1854 columns = [_('Encounter type')],
1855 data = [ e[1] for e in enc_types ]
1856 )
1857
1859 gmCfgWidgets.configure_boolean_option (
1860 parent = self,
1861 question = _(
1862 'Do you want GNUmed to show the encounter\n'
1863 'details editor when changing the active patient ?'
1864 ),
1865 option = 'encounter.show_editor_before_patient_change',
1866 button_tooltips = [
1867 _('Yes, show the encounter editor if it seems appropriate.'),
1868 _('No, never show the encounter editor even if it would seem useful.')
1869 ]
1870 )
1871
1876
1877 gmCfgWidgets.configure_string_option (
1878 message = _(
1879 'When a patient is activated GNUmed checks the\n'
1880 'chart for encounters lacking any entries.\n'
1881 '\n'
1882 'Any such encounters older than what you set\n'
1883 'here will be removed from the medical record.\n'
1884 '\n'
1885 'To effectively disable removal of such encounters\n'
1886 'set this option to an improbable value.\n'
1887 ),
1888 option = 'encounter.ttl_if_empty',
1889 bias = 'user',
1890 default_value = '1 week',
1891 validator = is_valid
1892 )
1893
1898
1899 gmCfgWidgets.configure_string_option (
1900 message = _(
1901 'When a patient is activated GNUmed checks the\n'
1902 'age of the most recent encounter.\n'
1903 '\n'
1904 'If that encounter is younger than this age\n'
1905 'the existing encounter will be continued.\n'
1906 '\n'
1907 '(If it is really old a new encounter is\n'
1908 ' started, or else GNUmed will ask you.)\n'
1909 ),
1910 option = 'encounter.minimum_ttl',
1911 bias = 'user',
1912 default_value = '1 hour 30 minutes',
1913 validator = is_valid
1914 )
1915
1920
1921 gmCfgWidgets.configure_string_option (
1922 message = _(
1923 'When a patient is activated GNUmed checks the\n'
1924 'age of the most recent encounter.\n'
1925 '\n'
1926 'If that encounter is older than this age\n'
1927 'GNUmed will always start a new encounter.\n'
1928 '\n'
1929 '(If it is very recent the existing encounter\n'
1930 ' is continued, or else GNUmed will ask you.)\n'
1931 ),
1932 option = 'encounter.maximum_ttl',
1933 bias = 'user',
1934 default_value = '6 hours',
1935 validator = is_valid
1936 )
1937
1946
1947 gmCfgWidgets.configure_string_option (
1948 message = _(
1949 'At any time there can only be one open (ongoing)\n'
1950 'episode for each health issue.\n'
1951 '\n'
1952 'When you try to open (add data to) an episode on a health\n'
1953 'issue GNUmed will check for an existing open episode on\n'
1954 'that issue. If there is any it will check the age of that\n'
1955 'episode. The episode is closed if it has been dormant (no\n'
1956 'data added, that is) for the period of time (in days) you\n'
1957 'set here.\n'
1958 '\n'
1959 "If the existing episode hasn't been dormant long enough\n"
1960 'GNUmed will consult you what to do.\n'
1961 '\n'
1962 'Enter maximum episode dormancy in DAYS:'
1963 ),
1964 option = 'episode.ttl',
1965 bias = 'user',
1966 default_value = 60,
1967 validator = is_valid
1968 )
1969
2000
2015
2040
2052
2053 gmCfgWidgets.configure_string_option (
2054 message = _(
2055 'GNUmed can check for new releases being available. To do\n'
2056 'so it needs to load version information from an URL.\n'
2057 '\n'
2058 'The default URL is:\n'
2059 '\n'
2060 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
2061 '\n'
2062 'but you can configure any other URL locally. Note\n'
2063 'that you must enter the location as a valid URL.\n'
2064 'Depending on the URL the client will need online\n'
2065 'access when checking for updates.'
2066 ),
2067 option = u'horstspace.update.url',
2068 bias = u'workplace',
2069 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
2070 validator = is_valid
2071 )
2072
2090
2107
2124
2135
2136 gmCfgWidgets.configure_string_option (
2137 message = _(
2138 'GNUmed can show the document review dialog after\n'
2139 'calling the appropriate viewer for that document.\n'
2140 '\n'
2141 'Select the conditions under which you want\n'
2142 'GNUmed to do so:\n'
2143 '\n'
2144 ' 0: never display the review dialog\n'
2145 ' 1: always display the dialog\n'
2146 ' 2: only if there is no previous review by me\n'
2147 ' 3: only if there is no previous review at all\n'
2148 ' 4: only if there is no review by the responsible reviewer\n'
2149 '\n'
2150 'Note that if a viewer is configured to not block\n'
2151 'GNUmed during document display the review dialog\n'
2152 'will actually appear in parallel to the viewer.'
2153 ),
2154 option = u'horstspace.document_viewer.review_after_display',
2155 bias = u'user',
2156 default_value = 3,
2157 validator = is_valid
2158 )
2159
2161
2162
2163 master_data_lists = [
2164 'adr',
2165 'billables',
2166 'drugs',
2167 'hints',
2168 'codes',
2169 'communication_channel_types',
2170 'substances_in_brands',
2171 'substances',
2172 'labs',
2173 'form_templates',
2174 'doc_types',
2175 'enc_types',
2176 'text_expansions',
2177 'meta_test_types',
2178 'orgs',
2179 'patient_tags',
2180 'provinces',
2181 'db_translations',
2182 'ref_data_sources',
2183 'test_types',
2184 'test_panels',
2185 'vacc_indications',
2186 'vaccines',
2187 'workplaces'
2188 ]
2189
2190 master_data_list_names = {
2191 'adr': _('Addresses (likely slow)'),
2192 'drugs': _('Branded drugs (as marketed)'),
2193 'hints': _('Clinical hints'),
2194 'codes': _('Codes and their respective terms'),
2195 'communication_channel_types': _('Communication channel types'),
2196 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2197 'labs': _('Diagnostic organizations (path labs, ...)'),
2198 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2199 'doc_types': _('Document types'),
2200 'enc_types': _('Encounter types'),
2201 'text_expansions': _('Keyword based text expansion macros'),
2202 'meta_test_types': _('Meta test/measurement types'),
2203 'orgs': _('Organizations with their units, addresses, and comm channels'),
2204 'patient_tags': _('Patient tags'),
2205 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2206 'db_translations': _('String translations in the database'),
2207 'test_types': _('Test/measurement types'),
2208 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2209 'vaccines': _('Vaccines'),
2210 'workplaces': _('Workplace profiles (which plugins to load)'),
2211 'substances': _('Consumable substances'),
2212 'billables': _('Billable items'),
2213 'ref_data_sources': _('Reference data sources'),
2214 'test_panels': _('Test/measurement panels/profiles')
2215 }
2216
2217 map_list2handler = {
2218 'form_templates': gmFormWidgets.manage_form_templates,
2219 'doc_types': gmDocumentWidgets.manage_document_types,
2220 'text_expansions': gmKeywordExpansionWidgets.configure_keyword_text_expansion,
2221 'db_translations': gmI18nWidgets.manage_translations,
2222 'codes': gmCodingWidgets.browse_coded_terms,
2223 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2224 'provinces': gmAddressWidgets.manage_provinces,
2225 'workplaces': gmPraxisWidgets.configure_workplace_plugins,
2226 'drugs': gmMedicationWidgets.manage_branded_drugs,
2227 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2228 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2229 'test_types': gmMeasurementWidgets.manage_measurement_types,
2230 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2231 'vaccines': gmVaccWidgets.manage_vaccines,
2232 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2233 'orgs': gmOrganizationWidgets.manage_orgs,
2234 'adr': gmAddressWidgets.manage_addresses,
2235 'substances': gmMedicationWidgets.manage_consumable_substances,
2236 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2237 'communication_channel_types': gmContactWidgets.manage_comm_channel_types,
2238 'billables': gmBillingWidgets.manage_billables,
2239 'ref_data_sources': gmCodingWidgets.browse_data_sources,
2240 'hints': gmProviderInboxWidgets.browse_dynamic_hints,
2241 'test_panels': gmMeasurementWidgets.manage_test_panels
2242 }
2243
2244
2245 def edit(item):
2246 try: map_list2handler[item](parent = self)
2247 except KeyError: pass
2248 return False
2249
2250
2251 gmListWidgets.get_choices_from_list (
2252 parent = self,
2253 caption = _('Master data management'),
2254 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2255 data = master_data_lists,
2256 columns = [_('Select the list you want to manage:')],
2257 edit_callback = edit,
2258 single_selection = True,
2259 ignore_OK_button = True
2260 )
2261
2264
2266
2267 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2268 if found:
2269 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2270 return
2271
2272 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2273 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking = False)
2274 return
2275
2276 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2277 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2278 if found:
2279 gmShellAPI.run_command_in_shell(cmd, blocking = False)
2280 return
2281
2282 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2283
2285
2286 curr_pat = gmPerson.gmCurrentPatient()
2287
2288 arriba = gmArriba.cArriba()
2289 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2290 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2291 return
2292
2293
2294 if curr_pat is None:
2295 return
2296
2297 if arriba.pdf_result is None:
2298 return
2299
2300 doc = gmDocumentWidgets.save_file_as_new_document (
2301 parent = self,
2302 filename = arriba.pdf_result,
2303 document_type = _('risk assessment')
2304 )
2305
2306 try: os.remove(arriba.pdf_result)
2307 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2308
2309 if doc is None:
2310 return
2311
2312 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2313 doc.save()
2314
2315 try:
2316 open(arriba.xml_result).close()
2317 part = doc.add_part(file = arriba.xml_result)
2318 except StandardError:
2319 _log.exception('error accessing [%s]', arriba.xml_result)
2320 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2321
2322 if part is None:
2323 return
2324
2325 part['obj_comment'] = u'XML-Daten'
2326 part['filename'] = u'arriba-result.xml'
2327 part.save()
2328
2330
2331 dbcfg = gmCfg.cCfgSQL()
2332 cmd = dbcfg.get2 (
2333 option = u'external.tools.acs_risk_calculator_cmd',
2334 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
2335 bias = 'user'
2336 )
2337
2338 if cmd is None:
2339 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2340 return
2341
2342 cwd = os.path.expanduser(os.path.join('~', '.gnumed'))
2343 try:
2344 subprocess.check_call (
2345 args = (cmd,),
2346 close_fds = True,
2347 cwd = cwd
2348 )
2349 except (OSError, ValueError, subprocess.CalledProcessError):
2350 _log.exception('there was a problem executing [%s]', cmd)
2351 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2352 return
2353
2354 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2355 for pdf in pdfs:
2356 try:
2357 open(pdf).close()
2358 except:
2359 _log.exception('error accessing [%s]', pdf)
2360 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2361 continue
2362
2363 doc = gmDocumentWidgets.save_file_as_new_document (
2364 parent = self,
2365 filename = pdf,
2366 document_type = u'risk assessment'
2367 )
2368
2369 try:
2370 os.remove(pdf)
2371 except StandardError:
2372 _log.exception('cannot remove [%s]', pdf)
2373
2374 if doc is None:
2375 continue
2376 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2377 doc.save()
2378
2379 return
2380
2383
2386
2389
2391 dlg = gmSnellen.cSnellenCfgDlg()
2392 if dlg.ShowModal() != wx.ID_OK:
2393 return
2394
2395 frame = gmSnellen.cSnellenChart (
2396 width = dlg.vals[0],
2397 height = dlg.vals[1],
2398 alpha = dlg.vals[2],
2399 mirr = dlg.vals[3],
2400 parent = None
2401 )
2402 frame.CentreOnScreen(wx.BOTH)
2403
2404
2405 frame.Show(True)
2406
2407
2410
2413
2416
2417
2418
2422
2425
2426
2427
2429 wx.CallAfter(self.__save_screenshot)
2430 evt.Skip()
2431
2433
2434 time.sleep(0.5)
2435
2436 rect = self.GetRect()
2437
2438
2439 if sys.platform == 'linux2':
2440 client_x, client_y = self.ClientToScreen((0, 0))
2441 border_width = client_x - rect.x
2442 title_bar_height = client_y - rect.y
2443
2444 if self.GetMenuBar():
2445 title_bar_height /= 2
2446 rect.width += (border_width * 2)
2447 rect.height += title_bar_height + border_width
2448
2449 wdc = wx.ScreenDC()
2450 mdc = wx.MemoryDC()
2451 img = wx.EmptyBitmap(rect.width, rect.height)
2452 mdc.SelectObject(img)
2453 mdc.Blit (
2454 0, 0,
2455 rect.width, rect.height,
2456 wdc,
2457 rect.x, rect.y
2458 )
2459
2460
2461 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2462 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2463 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2464
2466
2467 raise ValueError('raised ValueError to test exception handling')
2468
2470 raise gmExceptions.AccessDenied (
2471 _('[-9999]: <access violation test error>'),
2472 source = u'GNUmed code',
2473 code = -9999,
2474 details = _('This is a deliberate AcessDenied exception thrown to test the handling of access violations by means of a decorator.')
2475 )
2476
2477 @gmAccessPermissionWidgets.verify_minimum_required_role('admin', activity = _('testing access check for non-existant <admin> role'))
2479 raise gmExceptions.AccessDenied (
2480 _('[-9999]: <access violation test error>'),
2481 source = u'GNUmed code',
2482 code = -9999,
2483 details = _('This is a deliberate AcessDenied exception. You should not see this message because the role is checked in a decorator.')
2484 )
2485
2487 import wx.lib.inspection
2488 wx.lib.inspection.InspectionTool().Show()
2489
2492
2495
2498
2501
2508
2512
2515
2518
2525
2530
2532 name = os.path.basename(gmLog2._logfile_name)
2533 name, ext = os.path.splitext(name)
2534 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2535 new_path = os.path.expanduser(os.path.join('~', 'gnumed'))
2536
2537 dlg = wx.FileDialog (
2538 parent = self,
2539 message = _("Save current log as..."),
2540 defaultDir = new_path,
2541 defaultFile = new_name,
2542 wildcard = "%s (*.log)|*.log" % _("log files"),
2543 style = wx.SAVE
2544 )
2545 choice = dlg.ShowModal()
2546 new_name = dlg.GetPath()
2547 dlg.Destroy()
2548 if choice != wx.ID_OK:
2549 return True
2550
2551 _log.warning('syncing log file for backup to [%s]', new_name)
2552 gmLog2.flush()
2553 shutil.copy2(gmLog2._logfile_name, new_name)
2554 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2555
2558
2559
2560
2562 """This is the wx.EVT_CLOSE handler.
2563
2564 - framework still functional
2565 """
2566 _log.debug('gmTopLevelFrame.OnClose() start')
2567 self._clean_exit()
2568 self.Destroy()
2569 _log.debug('gmTopLevelFrame.OnClose() end')
2570 return True
2571
2577
2582
2590
2597
2604
2611
2621
2629
2637
2645
2653
2654 @gmAccessPermissionWidgets.verify_minimum_required_role('doctor', activity = _('manage vaccinations'))
2663
2672
2680
2687
2704
2707
2710
2712
2713 pat = gmPerson.gmCurrentPatient()
2714 if not pat.connected:
2715 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2716 return False
2717
2718 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2719 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', pat['dirname']))
2720 gmTools.mkdir(aDefDir)
2721
2722 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2723 dlg = wx.FileDialog (
2724 parent = self,
2725 message = _("Save patient's EMR journal as..."),
2726 defaultDir = aDefDir,
2727 defaultFile = fname,
2728 wildcard = aWildcard,
2729 style = wx.SAVE
2730 )
2731 choice = dlg.ShowModal()
2732 fname = dlg.GetPath()
2733 dlg.Destroy()
2734 if choice != wx.ID_OK:
2735 return True
2736
2737 _log.debug('exporting EMR journal to [%s]' % fname)
2738
2739 exporter = gmPatientExporter.cEMRJournalExporter()
2740
2741 wx.BeginBusyCursor()
2742 try:
2743 fname = exporter.export_to_file(filename = fname)
2744 except:
2745 wx.EndBusyCursor()
2746 gmGuiHelpers.gm_show_error (
2747 _('Error exporting patient EMR as chronological journal.'),
2748 _('EMR journal export')
2749 )
2750 raise
2751 wx.EndBusyCursor()
2752
2753 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2754
2755 return True
2756
2763
2765 curr_pat = gmPerson.gmCurrentPatient()
2766 if not curr_pat.connected:
2767 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2768 return
2769
2770 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2771 if tag is None:
2772 return
2773
2774 tag = curr_pat.add_tag(tag['pk_tag_image'])
2775 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2776 comment = wx.GetTextFromUser (
2777 message = msg,
2778 caption = _('Editing tag comment'),
2779 default_value = gmTools.coalesce(tag['comment'], u''),
2780 parent = self
2781 )
2782
2783 if comment == u'':
2784 return
2785
2786 if comment.strip() == tag['comment']:
2787 return
2788
2789 if comment == u' ':
2790 tag['comment'] = None
2791 else:
2792 tag['comment'] = comment.strip()
2793
2794 tag.save()
2795
2805
2807 curr_pat = gmPerson.gmCurrentPatient()
2808 if not curr_pat.connected:
2809 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2810 return False
2811 enc = 'cp850'
2812 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'current-patient.gdt'))
2813 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2814 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2815
2818
2821
2829
2837
2840
2847
2851
2854
2857
2860
2863
2868
2870 """Cleanup helper.
2871
2872 - should ALWAYS be called when this program is
2873 to be terminated
2874 - ANY code that should be executed before a
2875 regular shutdown should go in here
2876 - framework still functional
2877 """
2878 _log.debug('gmTopLevelFrame._clean_exit() start')
2879
2880
2881 listener = gmBackendListener.gmBackendListener()
2882 try:
2883 listener.shutdown()
2884 except:
2885 _log.exception('cannot stop backend notifications listener thread')
2886
2887
2888 if _scripting_listener is not None:
2889 try:
2890 _scripting_listener.shutdown()
2891 except:
2892 _log.exception('cannot stop scripting listener thread')
2893
2894
2895 self.clock_update_timer.Stop()
2896 gmTimer.shutdown()
2897 gmPhraseWheel.shutdown()
2898
2899
2900 for call_back in self.__pre_exit_callbacks:
2901 try:
2902 call_back()
2903 except:
2904 print "*** pre-exit callback failed ***"
2905 print call_back
2906 _log.exception('callback [%s] failed', call_back)
2907
2908
2909 gmDispatcher.send(u'application_closing')
2910
2911
2912 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2913
2914
2915 curr_width, curr_height = self.GetClientSizeTuple()
2916 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2917 dbcfg = gmCfg.cCfgSQL()
2918 dbcfg.set (
2919 option = 'main.window.width',
2920 value = curr_width,
2921 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
2922 )
2923 dbcfg.set (
2924 option = 'main.window.height',
2925 value = curr_height,
2926 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace
2927 )
2928
2929 if _cfg.get(option = 'debug'):
2930 print '---=== GNUmed shutdown ===---'
2931 try:
2932 print _('You have to manually close this window to finalize shutting down GNUmed.')
2933 print _('This is so that you can inspect the console output at your leisure.')
2934 except UnicodeEncodeError:
2935 print 'You have to manually close this window to finalize shutting down GNUmed.'
2936 print 'This is so that you can inspect the console output at your leisure.'
2937 print '---=== GNUmed shutdown ===---'
2938
2939
2940 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2941
2942
2943 import threading
2944 _log.debug("%s active threads", threading.activeCount())
2945 for t in threading.enumerate():
2946 _log.debug('thread %s', t)
2947
2948 _log.debug('gmTopLevelFrame._clean_exit() end')
2949
2950
2951
2953
2954 if _cfg.get(option = 'slave'):
2955 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s in %%(site)s of %%(prax)s] (%s:%s)' % (
2956 _cfg.get(option = 'slave personality'),
2957 _cfg.get(option = 'xml-rpc port')
2958 )
2959 else:
2960 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s in %(site)s of %(prax)s]'
2961
2963 """Update title of main window based on template.
2964
2965 This gives nice tooltips on iconified GNUmed instances.
2966
2967 User research indicates that in the title bar people want
2968 the date of birth, not the age, so please stick to this
2969 convention.
2970 """
2971 args = {}
2972
2973 pat = gmPerson.gmCurrentPatient()
2974 if pat.connected:
2975 args['pat'] = u'%s %s %s (%s) #%d' % (
2976 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2977 pat['firstnames'],
2978 pat['lastnames'],
2979 pat.get_formatted_dob(format = '%Y %b %d', encoding = gmI18N.get_encoding()),
2980 pat['pk_identity']
2981 )
2982 else:
2983 args['pat'] = _('no patient')
2984
2985 args['prov'] = u'%s%s.%s' % (
2986 gmTools.coalesce(_provider['title'], u'', u'%s '),
2987 _provider['firstnames'][:1],
2988 _provider['lastnames']
2989 )
2990
2991 praxis = gmPraxis.gmCurrentPraxisBranch()
2992 args['wp'] = praxis.active_workplace
2993 args['site'] = praxis['branch']
2994 args['prax'] = praxis['praxis']
2995
2996 self.SetTitle(self.__title_template % args)
2997
2998
3000 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
3001 sb.SetStatusWidths([-1, 225])
3002
3003 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
3004 self._cb_update_clock()
3005
3006 self.clock_update_timer.Start(milliseconds = 1000)
3007
3009 """Displays date and local time in the second slot of the status bar"""
3010 t = time.localtime(time.time())
3011 st = time.strftime('%Y %b %d %H:%M:%S', t).decode(gmI18N.get_encoding(), 'replace')
3012 self.SetStatusText(st, 1)
3013
3015 """Lock GNUmed client against unauthorized access"""
3016
3017
3018
3019 return
3020
3022 """Unlock the main notebook widgets
3023 As long as we are not logged into the database backend,
3024 all pages but the 'login' page of the main notebook widget
3025 are locked; i.e. not accessible by the user
3026 """
3027
3028
3029
3030
3031
3032 return
3033
3035 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
3036
3038
3040
3041 self.__starting_up = True
3042
3043 gmExceptionHandlingWidgets.install_wx_exception_handler()
3044 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
3045
3046
3047 self.SetAppName(u'gnumed')
3048 self.SetVendorName(u'The GNUmed Development Community.')
3049 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3050 paths.init_paths(wx = wx, app_name = u'gnumed')
3051
3052 if not self.__setup_prefs_file():
3053 return False
3054
3055 gmExceptionHandlingWidgets.set_sender_email(gmPraxis.gmCurrentPraxisBranch().user_email)
3056
3057 self.__guibroker = gmGuiBroker.GuiBroker()
3058 self.__setup_platform()
3059
3060 if not self.__establish_backend_connection():
3061 return False
3062 if not self.__verify_db_account():
3063 return False
3064 if not self.__verify_praxis_branch():
3065 return False
3066
3067 self.__check_db_lang()
3068 self.__update_workplace_list()
3069
3070 if not _cfg.get(option = 'skip-update-check'):
3071 self.__check_for_updates()
3072
3073 if _cfg.get(option = 'slave'):
3074 if not self.__setup_scripting_listener():
3075 return False
3076
3077
3078 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
3079 frame.CentreOnScreen(wx.BOTH)
3080 self.SetTopWindow(frame)
3081 frame.Show(True)
3082
3083 if _cfg.get(option = 'debug'):
3084 self.RedirectStdio()
3085 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
3086
3087
3088 print '---=== GNUmed startup ===---'
3089 print _('redirecting STDOUT/STDERR to this log window')
3090 print '---=== GNUmed startup ===---'
3091
3092 self.__setup_user_activity_timer()
3093 self.__register_events()
3094
3095 wx.CallAfter(self._do_after_init)
3096
3097 return True
3098
3100 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
3101
3102 - after destroying all application windows and controls
3103 - before wx.Windows internal cleanup
3104 """
3105 _log.debug('gmApp.OnExit() start')
3106
3107 self.__shutdown_user_activity_timer()
3108
3109 if _cfg.get(option = 'debug'):
3110 self.RestoreStdio()
3111 sys.stdin = sys.__stdin__
3112 sys.stdout = sys.__stdout__
3113 sys.stderr = sys.__stderr__
3114
3115 _log.debug('gmApp.OnExit() end')
3116
3118 wx.Bell()
3119 wx.Bell()
3120 wx.Bell()
3121 _log.warning('unhandled event detected: QUERY_END_SESSION')
3122 _log.info('we should be saving ourselves from here')
3123 gmLog2.flush()
3124 print "unhandled event detected: QUERY_END_SESSION"
3125
3127 wx.Bell()
3128 wx.Bell()
3129 wx.Bell()
3130 _log.warning('unhandled event detected: END_SESSION')
3131 gmLog2.flush()
3132 print "unhandled event detected: END_SESSION"
3133
3144
3146 self.user_activity_detected = True
3147 evt.Skip()
3148
3150
3151 if self.user_activity_detected:
3152 self.elapsed_inactivity_slices = 0
3153 self.user_activity_detected = False
3154 self.elapsed_inactivity_slices += 1
3155 else:
3156 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
3157
3158 pass
3159
3160 self.user_activity_timer.Start(oneShot = True)
3161
3162
3163
3165 try:
3166 kwargs['originated_in_database']
3167 print '==> got notification from database "%s":' % kwargs['signal']
3168 except KeyError:
3169 print '==> received signal from client: "%s"' % kwargs['signal']
3170
3171 del kwargs['signal']
3172 for key in kwargs.keys():
3173 print ' [%s]: %s' % (key, kwargs[key])
3174
3181
3183 self.user_activity_detected = True
3184 self.elapsed_inactivity_slices = 0
3185
3186 self.max_user_inactivity_slices = 15
3187 self.user_activity_timer = gmTimer.cTimer (
3188 callback = self._on_user_activity_timer_expired,
3189 delay = 2000
3190 )
3191 self.user_activity_timer.Start(oneShot=True)
3192
3194 try:
3195 self.user_activity_timer.Stop()
3196 del self.user_activity_timer
3197 except:
3198 pass
3199
3201 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
3202 wx.EVT_END_SESSION(self, self._on_end_session)
3203
3204
3205
3206
3207
3208 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
3209
3210 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
3211 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
3212
3213 if _cfg.get(option = 'debug'):
3214 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
3215 _log.debug('connected signal monitor')
3216
3232
3245
3274
3276
3277 if not gmPraxisWidgets.set_active_praxis_branch(no_parent = True):
3278 return False
3279
3280 login = gmPG2.get_default_login()
3281 msg = u'\n'
3282 msg += _('Database <%s> on <%s>') % (
3283 login.database,
3284 gmTools.coalesce(login.host, u'localhost')
3285 )
3286 msg += u'\n\n'
3287
3288 praxis = gmPraxis.gmCurrentPraxisBranch()
3289 msg += _('Branch "%s" of praxis "%s"\n') % (
3290 praxis['branch'],
3291 praxis['praxis']
3292 )
3293 msg += u'\n\n'
3294
3295 banner = praxis.db_logon_banner
3296 if banner.strip() == u'':
3297 return True
3298 msg += banner
3299 msg += u'\n\n'
3300
3301 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3302 None,
3303 -1,
3304 caption = _('Verifying database'),
3305 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3306 button_defs = [
3307 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3308 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3309 ]
3310 )
3311 log_on = dlg.ShowModal()
3312 dlg.Destroy()
3313 if log_on == wx.ID_YES:
3314 return True
3315 _log.info('user decided to not connect to this database')
3316 return False
3317
3331
3333 """Setup access to a config file for storing preferences."""
3334
3335 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3336
3337 candidates = []
3338 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3339 if explicit_file is not None:
3340 candidates.append(explicit_file)
3341
3342 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3343 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3344 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3345
3346 prefs_file = None
3347 for candidate in candidates:
3348 try:
3349 open(candidate, 'a+').close()
3350 prefs_file = candidate
3351 break
3352 except IOError:
3353 continue
3354
3355 if prefs_file is None:
3356 msg = _(
3357 'Cannot find configuration file in any of:\n'
3358 '\n'
3359 ' %s\n'
3360 'You may need to use the comand line option\n'
3361 '\n'
3362 ' --conf-file=<FILE>'
3363 ) % '\n '.join(candidates)
3364 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3365 return False
3366
3367 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3368 _log.info('user preferences file: %s', prefs_file)
3369
3370 return True
3371
3373
3374 from socket import error as SocketError
3375 from Gnumed.pycommon import gmScriptingListener
3376 from Gnumed.wxpython import gmMacro
3377
3378 slave_personality = gmTools.coalesce (
3379 _cfg.get (
3380 group = u'workplace',
3381 option = u'slave personality',
3382 source_order = [
3383 ('explicit', 'return'),
3384 ('workbase', 'return'),
3385 ('user', 'return'),
3386 ('system', 'return')
3387 ]
3388 ),
3389 u'gnumed-client'
3390 )
3391 _cfg.set_option(option = 'slave personality', value = slave_personality)
3392
3393
3394 port = int (
3395 gmTools.coalesce (
3396 _cfg.get (
3397 group = u'workplace',
3398 option = u'xml-rpc port',
3399 source_order = [
3400 ('explicit', 'return'),
3401 ('workbase', 'return'),
3402 ('user', 'return'),
3403 ('system', 'return')
3404 ]
3405 ),
3406 9999
3407 )
3408 )
3409 _cfg.set_option(option = 'xml-rpc port', value = port)
3410
3411 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3412 global _scripting_listener
3413 try:
3414 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3415 except SocketError, e:
3416 _log.exception('cannot start GNUmed XML-RPC server')
3417 gmGuiHelpers.gm_show_error (
3418 aMessage = (
3419 'Cannot start the GNUmed server:\n'
3420 '\n'
3421 ' [%s]'
3422 ) % e,
3423 aTitle = _('GNUmed startup')
3424 )
3425 return False
3426
3427 return True
3428
3449
3451 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3452 _log.warning("system locale is undefined (probably meaning 'C')")
3453 return True
3454
3455 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3456 curr_db_lang = rows[0]['lang']
3457 _log.debug("current database locale: [%s]" % curr_db_lang)
3458
3459 if curr_db_lang is None:
3460
3461 cmd = u'select i18n.set_curr_lang(%s)'
3462 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3463 if len(lang) == 0:
3464 continue
3465 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
3466 if rows[0][0]:
3467 _log.debug("Successfully set database language to [%s]." % lang)
3468 return True
3469 _log.error('Cannot set database language to [%s].' % lang)
3470
3471 return True
3472
3473 if curr_db_lang == gmI18N.system_locale_level['full']:
3474 _log.debug('Database locale (%s) up to date.' % curr_db_lang)
3475 return True
3476 if curr_db_lang == gmI18N.system_locale_level['country']:
3477 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (curr_db_lang, gmI18N.system_locale))
3478 return True
3479 if curr_db_lang == gmI18N.system_locale_level['language']:
3480 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (curr_db_lang, gmI18N.system_locale))
3481 return True
3482
3483 _log.warning('database locale [%s] does not match system locale [%s]' % (curr_db_lang, gmI18N.system_locale))
3484
3485 sys_lang2ignore = _cfg.get (
3486 group = u'backend',
3487 option = u'ignored mismatching system locale',
3488 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3489 )
3490 if gmI18N.system_locale == sys_lang2ignore:
3491 _log.info('configured to ignore system-to-database locale mismatch')
3492 return True
3493
3494
3495 msg = _(
3496 "The currently selected database language ('%s') does\n"
3497 "not match the current system language ('%s').\n"
3498 "\n"
3499 "Do you want to set the database language to '%s' ?\n"
3500 ) % (curr_db_lang, gmI18N.system_locale, gmI18N.system_locale)
3501 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3502 None,
3503 -1,
3504 caption = _('Checking database language settings'),
3505 question = msg,
3506 button_defs = [
3507 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3508 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3509 ],
3510 show_checkbox = True,
3511 checkbox_msg = _('Remember to ignore language mismatch'),
3512 checkbox_tooltip = _(
3513 'Checking this will make GNUmed remember your decision\n'
3514 'until the system language is changed.\n'
3515 '\n'
3516 'You can also reactivate this inquiry by removing the\n'
3517 'corresponding "ignore" option from the configuration file\n'
3518 '\n'
3519 ' [%s]'
3520 ) % _cfg.get(option = 'user_preferences_file')
3521 )
3522 decision = dlg.ShowModal()
3523 remember2ignore_this_mismatch = dlg._CHBOX_dont_ask_again.GetValue()
3524 dlg.Destroy()
3525
3526 if decision == wx.ID_NO:
3527 if not remember2ignore_this_mismatch:
3528 return True
3529 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3530 gmCfg2.set_option_in_INI_file (
3531 filename = _cfg.get(option = 'user_preferences_file'),
3532 group = 'backend',
3533 option = 'ignored mismatching system locale',
3534 value = gmI18N.system_locale
3535 )
3536 return True
3537
3538
3539 cmd = u'select i18n.set_curr_lang(%s)'
3540 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3541 if len(lang) == 0:
3542 continue
3543 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [lang]}], return_data = True)
3544 if rows[0][0]:
3545 _log.debug("Successfully set database language to [%s]." % lang)
3546 return True
3547 _log.error('Cannot set database language to [%s].' % lang)
3548
3549
3550 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3551 gmPG2.run_rw_queries(queries = [{
3552 'cmd': u'select i18n.force_curr_lang(%s)',
3553 'args': [gmI18N.system_locale_level['country']]
3554 }])
3555
3556 return True
3557
3559 try:
3560 kwargs['originated_in_database']
3561 print '==> got notification from database "%s":' % kwargs['signal']
3562 except KeyError:
3563 print '==> received signal from client: "%s"' % kwargs['signal']
3564
3565 del kwargs['signal']
3566 for key in kwargs.keys():
3567
3568 try: print ' [%s]: %s' % (key, kwargs[key])
3569 except: print 'cannot print signal information'
3570
3574
3585
3587
3588 if _cfg.get(option = 'debug'):
3589 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3590 _log.debug('gmDispatcher signal monitor activated')
3591
3592 setup_safe_wxEndBusyCursor()
3593
3594 wx.InitAllImageHandlers()
3595
3596
3597
3598 app = gmApp(redirect = False, clearSigInt = False)
3599 app.MainLoop()
3600
3601
3602
3603 if __name__ == '__main__':
3604
3605 from GNUmed.pycommon import gmI18N
3606 gmI18N.activate_locale()
3607 gmI18N.install_domain()
3608
3609 _log.info('Starting up as main module.')
3610 main()
3611
3612
3613