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 __version__ = "$Revision: 1.491 $"
13 __author__ = "H. Herb <hherb@gnumed.net>,\
14 K. Hilbert <Karsten.Hilbert@gmx.net>,\
15 I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
16 __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
17
18
19 import sys, time, os, os.path, datetime as pyDT
20 import shutil, logging, urllib2, subprocess, glob
21
22
23
24
25 if not hasattr(sys, 'frozen'):
26 import wxversion
27 wxversion.ensureMinimal('2.8-unicode', optionsRequired=True)
28
29 try:
30 import wx
31 except ImportError:
32 print "GNUmed startup: Cannot import wxPython library."
33 print "GNUmed startup: Make sure wxPython is installed."
34 print 'CRITICAL ERROR: Error importing wxPython. Halted.'
35 raise
36
37
38
39 version = int(u'%s%s' % (wx.MAJOR_VERSION, wx.MINOR_VERSION))
40 if (version < 28) or ('unicode' not in wx.PlatformInfo):
41 print "GNUmed startup: Unsupported wxPython version (%s: %s)." % (wx.VERSION_STRING, wx.PlatformInfo)
42 print "GNUmed startup: wxPython 2.8+ with unicode support is required."
43 print 'CRITICAL ERROR: Proper wxPython version not found. Halted.'
44 raise ValueError('wxPython 2.8+ with unicode support not found')
45
46
47
48 from Gnumed.pycommon import gmCfg, gmPG2, gmDispatcher, gmGuiBroker, gmI18N
49 from Gnumed.pycommon import gmExceptions, gmShellAPI, gmTools, gmDateTime
50 from Gnumed.pycommon import gmHooks, gmBackendListener, gmCfg2, gmLog2, gmNetworkTools
51
52 from Gnumed.business import gmPerson, gmClinicalRecord, gmSurgery, gmEMRStructItems
53 from Gnumed.business import gmVaccination
54 from Gnumed.business import gmArriba
55 from Gnumed.business import gmStaff
56
57 from Gnumed.exporters import gmPatientExporter
58
59 from Gnumed.wxpython import gmGuiHelpers, gmHorstSpace, gmEMRBrowser
60 from Gnumed.wxpython import gmDemographicsWidgets, gmEMRStructWidgets
61 from Gnumed.wxpython import gmPatSearchWidgets, gmAllergyWidgets, gmListWidgets
62 from Gnumed.wxpython import gmProviderInboxWidgets, gmCfgWidgets, gmExceptionHandlingWidgets
63 from Gnumed.wxpython import gmNarrativeWidgets, gmPhraseWheel, gmMedicationWidgets
64 from Gnumed.wxpython import gmStaffWidgets, gmDocumentWidgets, gmTimer, gmMeasurementWidgets
65 from Gnumed.wxpython import gmFormWidgets, gmSnellen
66 from Gnumed.wxpython import gmVaccWidgets
67 from Gnumed.wxpython import gmPersonContactWidgets
68 from Gnumed.wxpython import gmI18nWidgets
69 from Gnumed.wxpython import gmCodingWidgets
70 from Gnumed.wxpython import gmOrganizationWidgets
71 from Gnumed.wxpython import gmAuthWidgets
72 from Gnumed.wxpython import gmFamilyHistoryWidgets
73 from Gnumed.wxpython import gmDataPackWidgets
74 from Gnumed.wxpython import gmContactWidgets
75 from Gnumed.wxpython import gmAddressWidgets
76 from Gnumed.wxpython import gmBillingWidgets
77 from Gnumed.wxpython import gmTextExpansionWidgets
78
79
80 try:
81 _('dummy-no-need-to-translate-but-make-epydoc-happy')
82 except NameError:
83 _ = lambda x:x
84
85 _cfg = gmCfg2.gmCfgData()
86 _provider = None
87 _scripting_listener = None
88 _original_wxEndBusyCursor = None
89
90 _log = logging.getLogger('gm.main')
91 _log.info(__version__)
92 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
93
94
96 """GNUmed client's main windows frame.
97
98 This is where it all happens. Avoid popping up any other windows.
99 Most user interaction should happen to and from widgets within this frame
100 """
101
102 - def __init__(self, parent, id, title, size=wx.DefaultSize):
103 """You'll have to browse the source to understand what the constructor does
104 """
105 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
106
107 self.__setup_font()
108
109 self.__gb = gmGuiBroker.GuiBroker()
110 self.__pre_exit_callbacks = []
111 self.bar_width = -1
112 self.menu_id2plugin = {}
113
114 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
115
116 self.__setup_main_menu()
117 self.setup_statusbar()
118 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
119 gmTools.coalesce(_provider['title'], ''),
120 _provider['firstnames'][:1],
121 _provider['lastnames'],
122 _provider['short_alias'],
123 _provider['db_user']
124 ))
125
126 self.__set_window_title_template()
127 self.__update_window_title()
128
129
130
131
132
133 self.SetIcon(gmTools.get_icon(wx = wx))
134
135 self.__register_events()
136
137 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
138 self.vbox = wx.BoxSizer(wx.VERTICAL)
139 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
140
141 self.SetAutoLayout(True)
142 self.SetSizerAndFit(self.vbox)
143
144
145
146
147
148 self.__set_GUI_size()
149
150
152
153 font = self.GetFont()
154 _log.debug('system default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
155
156 desired_font_face = _cfg.get (
157 group = u'workplace',
158 option = u'client font',
159 source_order = [
160 ('explicit', 'return'),
161 ('workbase', 'return'),
162 ('local', 'return'),
163 ('user', 'return'),
164 ('system', 'return')
165 ]
166 )
167
168 fonts2try = []
169 if desired_font_face is not None:
170 _log.info('client is configured to use font [%s]', desired_font_face)
171 fonts2try.append(desired_font_face)
172
173 if wx.Platform == '__WXMSW__':
174 sane_font_face = u'DejaVu Sans'
175 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face)
176 fonts2try.append(sane_font_face)
177
178 if len(fonts2try) == 0:
179 return
180
181 for font_face in fonts2try:
182 success = font.SetFaceName(font_face)
183 if success:
184 self.SetFont(font)
185 _log.debug('switched font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
186 return
187 font = self.GetFont()
188 _log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face)
189
190 return
191
193 """Try to get previous window size from backend."""
194
195 cfg = gmCfg.cCfgSQL()
196
197
198 width = int(cfg.get2 (
199 option = 'main.window.width',
200 workplace = gmSurgery.gmCurrentPractice().active_workplace,
201 bias = 'workplace',
202 default = 800
203 ))
204
205
206 height = int(cfg.get2 (
207 option = 'main.window.height',
208 workplace = gmSurgery.gmCurrentPractice().active_workplace,
209 bias = 'workplace',
210 default = 600
211 ))
212
213 dw = wx.DisplaySize()[0]
214 dh = wx.DisplaySize()[1]
215
216 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
217 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
218 _log.debug('previous GUI size [%s:%s]', width, height)
219
220
221 if width > dw:
222 _log.debug('adjusting GUI width from %s to %s', width, dw)
223 width = dw
224
225 if height > dh:
226 _log.debug('adjusting GUI height from %s to %s', height, dh)
227 height = dh
228
229
230 if width < 100:
231 _log.debug('adjusting GUI width to minimum of 100 pixel')
232 width = 100
233 if height < 100:
234 _log.debug('adjusting GUI height to minimum of 100 pixel')
235 height = 100
236
237 _log.info('setting GUI to size [%s:%s]', width, height)
238
239 self.SetClientSize(wx.Size(width, height))
240
242 """Create the main menu entries.
243
244 Individual entries are farmed out to the modules.
245
246 menu item template:
247
248 item = menu_emr_edit.Append(-1, _(''), _(''))
249 self.Bind(wx.EVT_MENU, self__on_, item)
250 """
251 global wx
252 self.mainmenu = wx.MenuBar()
253 self.__gb['main.mainmenu'] = self.mainmenu
254
255
256 menu_gnumed = wx.Menu()
257
258 self.menu_plugins = wx.Menu()
259 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
260
261 ID = wx.NewId()
262 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
263 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
264
265 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
266 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
267
268
269 menu_gnumed.AppendSeparator()
270
271
272 menu_config = wx.Menu()
273
274 item = menu_config.Append(-1, _('All options'), _('List all options as configured in the database.'))
275 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
276
277
278 menu_cfg_db = wx.Menu()
279
280 ID = wx.NewId()
281 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
282 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
283
284 ID = wx.NewId()
285 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
286 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
287
288 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
289
290
291 menu_cfg_client = wx.Menu()
292
293 ID = wx.NewId()
294 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
295 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
296
297 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
298 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
299
300 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
301
302
303 menu_cfg_ui = wx.Menu()
304
305
306 menu_cfg_doc = wx.Menu()
307
308 ID = wx.NewId()
309 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
310 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
311
312 ID = wx.NewId()
313 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
314 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
315
316 ID = wx.NewId()
317 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
318 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
319
320 item = menu_cfg_doc.Append(-1, _('Generate UUID'), _('Whether to generate UUIDs for new documents.'))
321 self.Bind(wx.EVT_MENU, self.__on_configure_generate_doc_uuid, item)
322
323 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
324
325
326 menu_cfg_update = wx.Menu()
327
328 ID = wx.NewId()
329 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
330 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
331
332 ID = wx.NewId()
333 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
334 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
335
336 ID = wx.NewId()
337 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
338 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
339
340 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
341
342
343 menu_cfg_pat_search = wx.Menu()
344
345 ID = wx.NewId()
346 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
347 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
348
349 ID = wx.NewId()
350 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
351 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
352
353 ID = wx.NewId()
354 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
355 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
356
357 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
358 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
359
360 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
361 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
362
363 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
364
365
366 menu_cfg_soap_editing = wx.Menu()
367
368 ID = wx.NewId()
369 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
370 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
371
372 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.'))
373 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item)
374
375 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
376
377 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
378
379
380 menu_cfg_ext_tools = wx.Menu()
381
382
383
384
385
386 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
387 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
388
389 ID = wx.NewId()
390 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
391 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
392
393 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
394 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
395
396 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
397 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
398
399
400
401
402 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
403 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
404
405 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
406 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
407
408 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
409 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
410
411 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
412 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
413
414 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
415
416
417 menu_cfg_bill = wx.Menu()
418
419 item = menu_cfg_bill.Append(-1, _('Invoice template (no VAT)'), _('Select the template for printing an invoice without VAT.'))
420 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_template_no_vat, item)
421
422 item = menu_cfg_bill.Append(-1, _('Invoice template (with VAT)'), _('Select the template for printing an invoice with VAT.'))
423 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_template_with_vat, item)
424
425 item = menu_cfg_bill.Append(-1, _('Catalogs URL'), _('URL for billing catalogs (schedules of fees).'))
426 self.Bind(wx.EVT_MENU, self.__on_configure_billing_catalogs_url, item)
427
428
429 menu_cfg_emr = wx.Menu()
430
431 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
432 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
433
434 item = menu_cfg_emr.Append(-1, _('Primary doctor'), _('Select the primary doctor to fall back to for patients without one.'))
435 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
436
437
438 menu_cfg_encounter = wx.Menu()
439
440 ID = wx.NewId()
441 menu_cfg_encounter.Append(ID, _('Edit before patient change'), _('Edit encounter details before change of patient.'))
442 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
443
444 ID = wx.NewId()
445 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
446 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
447
448 ID = wx.NewId()
449 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
450 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
451
452 ID = wx.NewId()
453 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
454 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
455
456 ID = wx.NewId()
457 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
458 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
459
460 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
461
462
463 menu_cfg_episode = wx.Menu()
464
465 ID = wx.NewId()
466 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
467 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
468
469 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
470
471 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
472 menu_config.AppendMenu(wx.NewId(), _('Billing ...'), menu_cfg_bill)
473 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
474
475
476 menu_master_data = wx.Menu()
477
478 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
479 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
480
481 item = menu_master_data.Append(-1, _('Install data packs'), _('Install reference data from data packs.'))
482 self.Bind(wx.EVT_MENU, self.__on_install_data_packs, item)
483
484 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
485 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
486
487
488
489
490 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
491 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
492
493 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
494
495
496 menu_users = wx.Menu()
497
498 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
499 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
500
501 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
502 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
503
504 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
505 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
506
507 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
508
509
510 menu_gnumed.AppendSeparator()
511
512 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
513 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
514
515 self.mainmenu.Append(menu_gnumed, '&GNUmed')
516
517
518 menu_person = wx.Menu()
519
520 ID_CREATE_PATIENT = wx.NewId()
521 menu_person.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
522 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
523
524 ID_LOAD_EXT_PAT = wx.NewId()
525 menu_person.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
526 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
527
528 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
529 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
530
531 ID_DEL_PAT = wx.NewId()
532 menu_person.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
533 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
534
535 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
536 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
537
538 menu_person.AppendSeparator()
539
540 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
541 menu_person.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
542 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
543
544
545 ID = wx.NewId()
546 menu_person.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
547 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
548
549 menu_person.AppendSeparator()
550
551 self.mainmenu.Append(menu_person, '&Person')
552 self.__gb['main.patientmenu'] = menu_person
553
554
555 menu_emr = wx.Menu()
556
557
558 menu_emr_edit = wx.Menu()
559
560 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'))
561 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
562
563 item = menu_emr_edit.Append(-1, _('&Episode'), _('Add an episode of illness to the EMR of the active patient'))
564 self.Bind(wx.EVT_MENU, self.__on_add_episode, item)
565
566 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
567 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
568
569 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
570 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
571
572 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
573 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
574
575 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospitalizations.'))
576 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
577
578 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
579 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
580
581 item = menu_emr_edit.Append(-1, _('&Measurements'), _('Add (a) measurement result(s) for the current patient.'))
582 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
583
584 item = menu_emr_edit.Append(-1, _('&Vaccinations'), _('Add (a) vaccination(s) for the current patient.'))
585 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
586
587 item = menu_emr_edit.Append(-1, _('&Family history (FHx)'), _('Manage family history.'))
588 self.Bind(wx.EVT_MENU, self.__on_manage_fhx, item)
589
590 item = menu_emr_edit.Append(-1, _('&Encounters'), _('List all encounters including empty ones.'))
591 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
592
593 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
594
595
596 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
597 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
598
599 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
600 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
601
602
603
604
605
606 item = menu_emr.Append(-1, _('Statistics'), _('Show a high-level statistic summary of the EMR.'))
607 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
608
609
610
611
612 menu_emr.AppendSeparator()
613
614
615 menu_emr_export = wx.Menu()
616
617 ID_EXPORT_EMR_ASCII = wx.NewId()
618 menu_emr_export.Append (
619 ID_EXPORT_EMR_ASCII,
620 _('Text document'),
621 _("Export the EMR of the active patient into a text file")
622 )
623 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
624
625 ID_EXPORT_EMR_JOURNAL = wx.NewId()
626 menu_emr_export.Append (
627 ID_EXPORT_EMR_JOURNAL,
628 _('Journal'),
629 _("Export the EMR of the active patient as a chronological journal into a text file")
630 )
631 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
632
633 ID_EXPORT_MEDISTAR = wx.NewId()
634 menu_emr_export.Append (
635 ID_EXPORT_MEDISTAR,
636 _('MEDISTAR import format'),
637 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
638 )
639 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
640
641 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
642
643 menu_emr.AppendSeparator()
644
645 self.mainmenu.Append(menu_emr, _("&EMR"))
646 self.__gb['main.emrmenu'] = menu_emr
647
648
649 menu_paperwork = wx.Menu()
650
651 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
652 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
653
654 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
655
656
657 self.menu_tools = wx.Menu()
658
659 item = self.menu_tools.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
660 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
661
662 ID_DICOM_VIEWER = wx.NewId()
663 viewer = _('no viewer installed')
664 if gmShellAPI.detect_external_binary(binary = 'ginkgocadx')[0]:
665 viewer = u'Ginkgo CADx'
666 elif os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
667 viewer = u'OsiriX'
668 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
669 viewer = u'Aeskulap'
670 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
671 viewer = u'AMIDE'
672 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
673 viewer = u'DicomScope'
674 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
675 viewer = u'(x)medcon'
676 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)
677 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
678 if viewer == _('no viewer installed'):
679 _log.info('neither of Ginkgo CADx / OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
680 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
681
682
683
684
685
686 ID = wx.NewId()
687 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
688 wx.EVT_MENU(self, ID, self.__on_snellen)
689
690 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
691 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
692
693 ID_DICOM_VIEWER = wx.NewId()
694 self.menu_tools.Append(ID_DICOM_VIEWER, u'arriba', _('arriba: cardiovascular risk assessment (%s).') % u'www.arriba-hausarzt.de')
695 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_arriba)
696 if not gmShellAPI.detect_external_binary(binary = 'arriba')[0]:
697 _log.info('<arriba> not found, disabling "arriba" menu item')
698 self.menu_tools.Enable(id = ID_DICOM_VIEWER, enable = False)
699
700
701
702 self.menu_tools.AppendSeparator()
703
704 self.mainmenu.Append(self.menu_tools, _("&Tools"))
705 self.__gb['main.toolsmenu'] = self.menu_tools
706
707
708 menu_knowledge = wx.Menu()
709
710
711 menu_drug_dbs = wx.Menu()
712
713 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
714 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
715
716
717
718
719
720
721 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
722
723 menu_id = wx.NewId()
724 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
725 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
726
727
728
729
730 ID_MEDICAL_LINKS = wx.NewId()
731 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
732 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
733
734 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
735 self.__gb['main.knowledgemenu'] = menu_knowledge
736
737
738 self.menu_office = wx.Menu()
739
740 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
741 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
742
743 self.menu_office.AppendSeparator()
744
745 item = self.menu_office.Append(-1, _('List bills'), _('List all bills across all patients.'))
746 self.Bind(wx.EVT_MENU, self.__on_show_all_bills, item)
747
748 self.mainmenu.Append(self.menu_office, _('&Office'))
749 self.__gb['main.officemenu'] = self.menu_office
750
751
752 help_menu = wx.Menu()
753
754 ID = wx.NewId()
755 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
756 wx.EVT_MENU(self, ID, self.__on_display_wiki)
757
758 ID = wx.NewId()
759 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
760 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
761
762 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
763 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
764
765 menu_debugging = wx.Menu()
766
767 ID_SCREENSHOT = wx.NewId()
768 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
769 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
770
771 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
772 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
773
774 ID = wx.NewId()
775 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
776 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
777
778 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
779 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
780
781 ID = wx.NewId()
782 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
783 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
784
785 ID_UNBLOCK = wx.NewId()
786 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
787 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
788
789 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
790 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
791
792
793
794
795 if _cfg.get(option = 'debug'):
796 ID_TOGGLE_PAT_LOCK = wx.NewId()
797 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
798 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
799
800 ID_TEST_EXCEPTION = wx.NewId()
801 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
802 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
803
804 ID = wx.NewId()
805 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
806 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
807 try:
808 import wx.lib.inspection
809 except ImportError:
810 menu_debugging.Enable(id = ID, enable = False)
811
812 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
813
814 help_menu.AppendSeparator()
815
816 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
817 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
818
819 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
820 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
821
822 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors'))
823 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item)
824
825 help_menu.AppendSeparator()
826
827 self.mainmenu.Append(help_menu, _("&Help"))
828
829 self.__gb['main.helpmenu'] = help_menu
830
831
832 self.SetMenuBar(self.mainmenu)
833
836
837
838
840 """register events we want to react to"""
841
842 wx.EVT_CLOSE(self, self.OnClose)
843 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
844 wx.EVT_END_SESSION(self, self._on_end_session)
845
846 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
847 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
848 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
849 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
850 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
851 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
852 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
853 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
854
855 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
856
857 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
858
859 _log.debug('registering plugin with menu system')
860 _log.debug(' generic name: %s', plugin_name)
861 _log.debug(' class name: %s', class_name)
862 _log.debug(' specific menu: %s', menu_name)
863 _log.debug(' menu item: %s', menu_item_name)
864
865
866 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
867 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
868 self.menu_id2plugin[item.Id] = class_name
869
870
871 if menu_name is not None:
872 menu = self.__gb['main.%smenu' % menu_name]
873 item = menu.Append(-1, menu_item_name, menu_help_string)
874 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
875 self.menu_id2plugin[item.Id] = class_name
876
877 return True
878
880 gmDispatcher.send (
881 signal = u'display_widget',
882 name = self.menu_id2plugin[evt.Id]
883 )
884
886 wx.Bell()
887 wx.Bell()
888 wx.Bell()
889 _log.warning('unhandled event detected: QUERY_END_SESSION')
890 _log.info('we should be saving ourselves from here')
891 gmLog2.flush()
892 print "unhandled event detected: QUERY_END_SESSION"
893
895 wx.Bell()
896 wx.Bell()
897 wx.Bell()
898 _log.warning('unhandled event detected: END_SESSION')
899 gmLog2.flush()
900 print "unhandled event detected: END_SESSION"
901
903 if not callable(callback):
904 raise TypeError(u'callback [%s] not callable' % callback)
905
906 self.__pre_exit_callbacks.append(callback)
907
908 - def _on_set_statustext_pubsub(self, context=None):
909 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
910 wx.CallAfter(self.SetStatusText, msg)
911
912 try:
913 if context.data['beep']:
914 wx.Bell()
915 except KeyError:
916 pass
917
918 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
919
920 if msg is None:
921 msg = _('programmer forgot to specify status message')
922
923 if loglevel is not None:
924 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
925
926 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
927 wx.CallAfter(self.SetStatusText, msg)
928
929 if beep:
930 wx.Bell()
931
933 wx.CallAfter(self.__on_db_maintenance_warning)
934
936
937 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
938 wx.Bell()
939 if not wx.GetApp().IsActive():
940 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
941
942 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
943
944 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
945 None,
946 -1,
947 caption = _('Database shutdown warning'),
948 question = _(
949 'The database will be shut down for maintenance\n'
950 'in a few minutes.\n'
951 '\n'
952 'In order to not suffer any loss of data you\n'
953 'will need to save your current work and log\n'
954 'out of this GNUmed client.\n'
955 ),
956 button_defs = [
957 {
958 u'label': _('Close now'),
959 u'tooltip': _('Close this GNUmed client immediately.'),
960 u'default': False
961 },
962 {
963 u'label': _('Finish work'),
964 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
965 u'default': True
966 }
967 ]
968 )
969 decision = dlg.ShowModal()
970 if decision == wx.ID_YES:
971 top_win = wx.GetApp().GetTopWindow()
972 wx.CallAfter(top_win.Close)
973
975 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
976
978
979 if not wx.GetApp().IsActive():
980 if urgent:
981 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
982 else:
983 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
984
985 if msg is not None:
986 self.SetStatusText(msg)
987
988 if urgent:
989 wx.Bell()
990
991 gmHooks.run_hook_script(hook = u'request_user_attention')
992
994 wx.CallAfter(self.__on_pat_name_changed)
995
997 self.__update_window_title()
998
1000 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
1001
1003 self.__update_window_title()
1004 try:
1005 gmHooks.run_hook_script(hook = u'post_patient_activation')
1006 except:
1007 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
1008 raise
1009
1011 return self.__sanity_check_encounter()
1012
1074
1075
1076
1079
1086
1087
1088
1104
1127
1129 from Gnumed.wxpython import gmAbout
1130 contribs = gmAbout.cContributorsDlg (
1131 parent = self,
1132 id = -1,
1133 title = _('GNUmed contributors'),
1134 size = wx.Size(400,600),
1135 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1136 )
1137 contribs.ShowModal()
1138 del contribs
1139 del gmAbout
1140
1141
1142
1144 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1145 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1146 self.Close(True)
1147 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1148
1151
1153 send = gmGuiHelpers.gm_show_question (
1154 _('This will send a notification about database downtime\n'
1155 'to all GNUmed clients connected to your database.\n'
1156 '\n'
1157 'Do you want to send the notification ?\n'
1158 ),
1159 _('Announcing database maintenance downtime')
1160 )
1161 if not send:
1162 return
1163 gmPG2.send_maintenance_notification()
1164
1165
1168
1169
1170
1183
1184 gmCfgWidgets.configure_string_option (
1185 message = _(
1186 'Some network installations cannot cope with loading\n'
1187 'documents of arbitrary size in one piece from the\n'
1188 'database (mainly observed on older Windows versions)\n.'
1189 '\n'
1190 'Under such circumstances documents need to be retrieved\n'
1191 'in chunks and reassembled on the client.\n'
1192 '\n'
1193 'Here you can set the size (in Bytes) above which\n'
1194 'GNUmed will retrieve documents in chunks. Setting this\n'
1195 'value to 0 will disable the chunking protocol.'
1196 ),
1197 option = 'horstspace.blob_export_chunk_size',
1198 bias = 'workplace',
1199 default_value = 1024 * 1024,
1200 validator = is_valid
1201 )
1202
1203
1204
1272
1276
1277
1278
1287
1288 gmCfgWidgets.configure_string_option (
1289 message = _(
1290 'When GNUmed cannot find an OpenOffice server it\n'
1291 'will try to start one. OpenOffice, however, needs\n'
1292 'some time to fully start up.\n'
1293 '\n'
1294 'Here you can set the time for GNUmed to wait for OOo.\n'
1295 ),
1296 option = 'external.ooo.startup_settle_time',
1297 bias = 'workplace',
1298 default_value = 2.0,
1299 validator = is_valid
1300 )
1301
1304
1319
1320 gmCfgWidgets.configure_string_option (
1321 message = _(
1322 'GNUmed will use this URL to access a website which lets\n'
1323 'you report an adverse drug reaction (ADR).\n'
1324 '\n'
1325 'If you leave this empty it will fall back\n'
1326 'to an URL for reporting ADRs in Germany.'
1327 ),
1328 option = 'external.urls.report_ADR',
1329 bias = 'user',
1330 default_value = german_default,
1331 validator = is_valid
1332 )
1333
1347
1348 gmCfgWidgets.configure_string_option (
1349 message = _(
1350 'GNUmed will use this URL to access a website which lets\n'
1351 'you report an adverse vaccination reaction (vADR).\n'
1352 '\n'
1353 'If you set it to a specific address that URL must be\n'
1354 'accessible now. If you leave it empty it will fall back\n'
1355 'to the URL for reporting other adverse drug reactions.'
1356 ),
1357 option = 'external.urls.report_vaccine_ADR',
1358 bias = 'user',
1359 default_value = german_default,
1360 validator = is_valid
1361 )
1362
1376
1377 gmCfgWidgets.configure_string_option (
1378 message = _(
1379 'GNUmed will use this URL to access an encyclopedia of\n'
1380 'measurement/lab methods from within the measurments grid.\n'
1381 '\n'
1382 'You can leave this empty but to set it to a specific\n'
1383 'address the URL must be accessible now.'
1384 ),
1385 option = 'external.urls.measurements_encyclopedia',
1386 bias = 'user',
1387 default_value = german_default,
1388 validator = is_valid
1389 )
1390
1404
1405 gmCfgWidgets.configure_string_option (
1406 message = _(
1407 'GNUmed will use this URL to access a page showing\n'
1408 'vaccination schedules.\n'
1409 '\n'
1410 'You can leave this empty but to set it to a specific\n'
1411 'address the URL must be accessible now.'
1412 ),
1413 option = 'external.urls.vaccination_plans',
1414 bias = 'user',
1415 default_value = german_default,
1416 validator = is_valid
1417 )
1418
1431
1432 gmCfgWidgets.configure_string_option (
1433 message = _(
1434 'Enter the shell command with which to start the\n'
1435 'the ACS risk assessment calculator.\n'
1436 '\n'
1437 'GNUmed will try to verify the path which may,\n'
1438 'however, fail if you are using an emulator such\n'
1439 'as Wine. Nevertheless, starting the calculator\n'
1440 'will work as long as the shell command is correct\n'
1441 'despite the failing test.'
1442 ),
1443 option = 'external.tools.acs_risk_calculator_cmd',
1444 bias = 'user',
1445 validator = is_valid
1446 )
1447
1450
1463
1464 gmCfgWidgets.configure_string_option (
1465 message = _(
1466 'Enter the shell command with which to start\n'
1467 'the FreeDiams drug database frontend.\n'
1468 '\n'
1469 'GNUmed will try to verify that path.'
1470 ),
1471 option = 'external.tools.freediams_cmd',
1472 bias = 'workplace',
1473 default_value = None,
1474 validator = is_valid
1475 )
1476
1489
1490 gmCfgWidgets.configure_string_option (
1491 message = _(
1492 'Enter the shell command with which to start the\n'
1493 'the IFAP drug database.\n'
1494 '\n'
1495 'GNUmed will try to verify the path which may,\n'
1496 'however, fail if you are using an emulator such\n'
1497 'as Wine. Nevertheless, starting IFAP will work\n'
1498 'as long as the shell command is correct despite\n'
1499 'the failing test.'
1500 ),
1501 option = 'external.ifap-win.shell_command',
1502 bias = 'workplace',
1503 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1504 validator = is_valid
1505 )
1506
1507
1508
1557
1558
1559
1576
1579
1582
1587
1588 gmCfgWidgets.configure_string_option (
1589 message = _(
1590 'When a patient is activated GNUmed checks the\n'
1591 "proximity of the patient's birthday.\n"
1592 '\n'
1593 'If the birthday falls within the range of\n'
1594 ' "today %s <the interval you set here>"\n'
1595 'GNUmed will remind you of the recent or\n'
1596 'imminent anniversary.'
1597 ) % u'\u2213',
1598 option = u'patient_search.dob_warn_interval',
1599 bias = 'user',
1600 default_value = '1 week',
1601 validator = is_valid
1602 )
1603
1605
1606 gmCfgWidgets.configure_boolean_option (
1607 parent = self,
1608 question = _(
1609 'When adding progress notes do you want to\n'
1610 'allow opening several unassociated, new\n'
1611 'episodes for a patient at once ?\n'
1612 '\n'
1613 'This can be particularly helpful when entering\n'
1614 'progress notes on entirely new patients presenting\n'
1615 'with a multitude of problems on their first visit.'
1616 ),
1617 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1618 button_tooltips = [
1619 _('Yes, allow for multiple new episodes concurrently.'),
1620 _('No, only allow editing one new episode at a time.')
1621 ]
1622 )
1623
1625
1626 gmCfgWidgets.configure_boolean_option (
1627 parent = self,
1628 question = _(
1629 'When activating a patient, do you want GNUmed to\n'
1630 'auto-open editors for all active problems that were\n'
1631 'touched upon during the current and the most recent\n'
1632 'encounter ?'
1633 ),
1634 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1635 button_tooltips = [
1636 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1637 _('No, only auto-open one editor for a new, unassociated problem.')
1638 ]
1639 )
1640
1686
1687
1688
1691
1694
1707
1708 gmCfgWidgets.configure_string_option (
1709 message = _(
1710 'GNUmed will use this URL to let you browse\n'
1711 'billing catalogs (schedules of fees).\n'
1712 '\n'
1713 'You can leave this empty but to set it to a specific\n'
1714 'address the URL must be accessible now.'
1715 ),
1716 option = 'external.urls.schedules_of_fees',
1717 bias = 'user',
1718 default_value = german_default,
1719 validator = is_valid
1720 )
1721
1722
1723
1726
1729
1743
1745 gmCfgWidgets.configure_boolean_option (
1746 parent = self,
1747 question = _(
1748 'Do you want GNUmed to show the encounter\n'
1749 'details editor when changing the active patient ?'
1750 ),
1751 option = 'encounter.show_editor_before_patient_change',
1752 button_tooltips = [
1753 _('Yes, show the encounter editor if it seems appropriate.'),
1754 _('No, never show the encounter editor even if it would seem useful.')
1755 ]
1756 )
1757
1762
1763 gmCfgWidgets.configure_string_option (
1764 message = _(
1765 'When a patient is activated GNUmed checks the\n'
1766 'chart for encounters lacking any entries.\n'
1767 '\n'
1768 'Any such encounters older than what you set\n'
1769 'here will be removed from the medical record.\n'
1770 '\n'
1771 'To effectively disable removal of such encounters\n'
1772 'set this option to an improbable value.\n'
1773 ),
1774 option = 'encounter.ttl_if_empty',
1775 bias = 'user',
1776 default_value = '1 week',
1777 validator = is_valid
1778 )
1779
1784
1785 gmCfgWidgets.configure_string_option (
1786 message = _(
1787 'When a patient is activated GNUmed checks the\n'
1788 'age of the most recent encounter.\n'
1789 '\n'
1790 'If that encounter is younger than this age\n'
1791 'the existing encounter will be continued.\n'
1792 '\n'
1793 '(If it is really old a new encounter is\n'
1794 ' started, or else GNUmed will ask you.)\n'
1795 ),
1796 option = 'encounter.minimum_ttl',
1797 bias = 'user',
1798 default_value = '1 hour 30 minutes',
1799 validator = is_valid
1800 )
1801
1806
1807 gmCfgWidgets.configure_string_option (
1808 message = _(
1809 'When a patient is activated GNUmed checks the\n'
1810 'age of the most recent encounter.\n'
1811 '\n'
1812 'If that encounter is older than this age\n'
1813 'GNUmed will always start a new encounter.\n'
1814 '\n'
1815 '(If it is very recent the existing encounter\n'
1816 ' is continued, or else GNUmed will ask you.)\n'
1817 ),
1818 option = 'encounter.maximum_ttl',
1819 bias = 'user',
1820 default_value = '6 hours',
1821 validator = is_valid
1822 )
1823
1832
1833 gmCfgWidgets.configure_string_option (
1834 message = _(
1835 'At any time there can only be one open (ongoing)\n'
1836 'episode for each health issue.\n'
1837 '\n'
1838 'When you try to open (add data to) an episode on a health\n'
1839 'issue GNUmed will check for an existing open episode on\n'
1840 'that issue. If there is any it will check the age of that\n'
1841 'episode. The episode is closed if it has been dormant (no\n'
1842 'data added, that is) for the period of time (in days) you\n'
1843 'set here.\n'
1844 '\n'
1845 "If the existing episode hasn't been dormant long enough\n"
1846 'GNUmed will consult you what to do.\n'
1847 '\n'
1848 'Enter maximum episode dormancy in DAYS:'
1849 ),
1850 option = 'episode.ttl',
1851 bias = 'user',
1852 default_value = 60,
1853 validator = is_valid
1854 )
1855
1886
1901
1926
1938
1939 gmCfgWidgets.configure_string_option (
1940 message = _(
1941 'GNUmed can check for new releases being available. To do\n'
1942 'so it needs to load version information from an URL.\n'
1943 '\n'
1944 'The default URL is:\n'
1945 '\n'
1946 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1947 '\n'
1948 'but you can configure any other URL locally. Note\n'
1949 'that you must enter the location as a valid URL.\n'
1950 'Depending on the URL the client will need online\n'
1951 'access when checking for updates.'
1952 ),
1953 option = u'horstspace.update.url',
1954 bias = u'workplace',
1955 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1956 validator = is_valid
1957 )
1958
1976
1993
2010
2021
2022 gmCfgWidgets.configure_string_option (
2023 message = _(
2024 'GNUmed can show the document review dialog after\n'
2025 'calling the appropriate viewer for that document.\n'
2026 '\n'
2027 'Select the conditions under which you want\n'
2028 'GNUmed to do so:\n'
2029 '\n'
2030 ' 0: never display the review dialog\n'
2031 ' 1: always display the dialog\n'
2032 ' 2: only if there is no previous review by me\n'
2033 ' 3: only if there is no previous review at all\n'
2034 ' 4: only if there is no review by the responsible reviewer\n'
2035 '\n'
2036 'Note that if a viewer is configured to not block\n'
2037 'GNUmed during document display the review dialog\n'
2038 'will actually appear in parallel to the viewer.'
2039 ),
2040 option = u'horstspace.document_viewer.review_after_display',
2041 bias = u'user',
2042 default_value = 3,
2043 validator = is_valid
2044 )
2045
2047
2048
2049 master_data_lists = [
2050 'adr',
2051 'billables',
2052 'drugs',
2053 'codes',
2054 'communication_channel_types',
2055 'substances_in_brands',
2056 'substances',
2057 'labs',
2058 'form_templates',
2059 'doc_types',
2060 'enc_types',
2061 'text_expansions',
2062 'meta_test_types',
2063 'orgs',
2064 'patient_tags',
2065 'provinces',
2066 'db_translations',
2067 'ref_data_sources',
2068 'test_types',
2069 'vacc_indications',
2070 'vaccines',
2071 'workplaces'
2072 ]
2073
2074 master_data_list_names = {
2075 'adr': _('Addresses (likely slow)'),
2076 'drugs': _('Branded drugs (as marketed)'),
2077 'codes': _('Codes and their respective terms'),
2078 'communication_channel_types': _('Communication channel types'),
2079 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2080 'labs': _('Diagnostic organizations (path labs, ...)'),
2081 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2082 'doc_types': _('Document types'),
2083 'enc_types': _('Encounter types'),
2084 'text_expansions': _('Keyword based text expansion macros'),
2085 'meta_test_types': _('Meta test/measurement types'),
2086 'orgs': _('Organizations with their units, addresses, and comm channels'),
2087 'patient_tags': _('Patient tags'),
2088 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2089 'db_translations': _('String translations in the database'),
2090 'test_types': _('Test/measurement types'),
2091 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2092 'vaccines': _('Vaccines'),
2093 'workplaces': _('Workplace profiles (which plugins to load)'),
2094 'substances': _('Consumable substances'),
2095 'billables': _('Billable items'),
2096 'ref_data_sources': _('Reference data sources')
2097 }
2098
2099 map_list2handler = {
2100 'form_templates': gmFormWidgets.manage_form_templates,
2101 'doc_types': gmDocumentWidgets.manage_document_types,
2102 'text_expansions': gmTextExpansionWidgets.configure_keyword_text_expansion,
2103 'db_translations': gmI18nWidgets.manage_translations,
2104 'codes': gmCodingWidgets.browse_coded_terms,
2105 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2106 'provinces': gmAddressWidgets.manage_provinces,
2107 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins,
2108 'drugs': gmMedicationWidgets.manage_branded_drugs,
2109 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2110 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2111 'test_types': gmMeasurementWidgets.manage_measurement_types,
2112 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2113 'vaccines': gmVaccWidgets.manage_vaccines,
2114 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2115 'orgs': gmOrganizationWidgets.manage_orgs,
2116 'adr': gmAddressWidgets.manage_addresses,
2117 'substances': gmMedicationWidgets.manage_consumable_substances,
2118 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2119 'communication_channel_types': gmContactWidgets.manage_comm_channel_types,
2120 'billables': gmBillingWidgets.manage_billables,
2121 'ref_data_sources': gmCodingWidgets.browse_data_sources
2122 }
2123
2124
2125 def edit(item):
2126 try: map_list2handler[item](parent = self)
2127 except KeyError: pass
2128 return False
2129
2130
2131 gmListWidgets.get_choices_from_list (
2132 parent = self,
2133 caption = _('Master data management'),
2134 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2135 data = master_data_lists,
2136 columns = [_('Select the list you want to manage:')],
2137 edit_callback = edit,
2138 single_selection = True,
2139 ignore_OK_button = True
2140 )
2141
2143
2144 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2145 if found:
2146 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2147 return
2148
2149 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2150 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking = False)
2151 return
2152
2153 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2154 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2155 if found:
2156 gmShellAPI.run_command_in_shell(cmd, blocking = False)
2157 return
2158
2159 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2160
2162
2163 curr_pat = gmPerson.gmCurrentPatient()
2164
2165 arriba = gmArriba.cArriba()
2166 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2167 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2168 return
2169
2170
2171 if curr_pat is None:
2172 return
2173
2174 if arriba.pdf_result is None:
2175 return
2176
2177 doc = gmDocumentWidgets.save_file_as_new_document (
2178 parent = self,
2179 filename = arriba.pdf_result,
2180 document_type = _('risk assessment')
2181 )
2182
2183 try: os.remove(arriba.pdf_result)
2184 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2185
2186 if doc is None:
2187 return
2188
2189 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2190 doc.save()
2191
2192 try:
2193 open(arriba.xml_result).close()
2194 part = doc.add_part(file = arriba.xml_result)
2195 except StandardError:
2196 _log.exception('error accessing [%s]', arriba.xml_result)
2197 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2198
2199 if part is None:
2200 return
2201
2202 part['obj_comment'] = u'XML-Daten'
2203 part['filename'] = u'arriba-result.xml'
2204 part.save()
2205
2207
2208 dbcfg = gmCfg.cCfgSQL()
2209 cmd = dbcfg.get2 (
2210 option = u'external.tools.acs_risk_calculator_cmd',
2211 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2212 bias = 'user'
2213 )
2214
2215 if cmd is None:
2216 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2217 return
2218
2219 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
2220 try:
2221 subprocess.check_call (
2222 args = (cmd,),
2223 close_fds = True,
2224 cwd = cwd
2225 )
2226 except (OSError, ValueError, subprocess.CalledProcessError):
2227 _log.exception('there was a problem executing [%s]', cmd)
2228 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2229 return
2230
2231 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2232 for pdf in pdfs:
2233 try:
2234 open(pdf).close()
2235 except:
2236 _log.exception('error accessing [%s]', pdf)
2237 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2238 continue
2239
2240 doc = gmDocumentWidgets.save_file_as_new_document (
2241 parent = self,
2242 filename = pdf,
2243 document_type = u'risk assessment'
2244 )
2245
2246 try:
2247 os.remove(pdf)
2248 except StandardError:
2249 _log.exception('cannot remove [%s]', pdf)
2250
2251 if doc is None:
2252 continue
2253 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2254 doc.save()
2255
2256 return
2257
2259 dlg = gmSnellen.cSnellenCfgDlg()
2260 if dlg.ShowModal() != wx.ID_OK:
2261 return
2262
2263 frame = gmSnellen.cSnellenChart (
2264 width = dlg.vals[0],
2265 height = dlg.vals[1],
2266 alpha = dlg.vals[2],
2267 mirr = dlg.vals[3],
2268 parent = None
2269 )
2270 frame.CentreOnScreen(wx.BOTH)
2271
2272
2273 frame.Show(True)
2274
2275
2278
2281
2284
2285
2286
2290
2293
2294
2295
2297 wx.CallAfter(self.__save_screenshot)
2298 evt.Skip()
2299
2301
2302 time.sleep(0.5)
2303
2304 rect = self.GetRect()
2305
2306
2307 if sys.platform == 'linux2':
2308 client_x, client_y = self.ClientToScreen((0, 0))
2309 border_width = client_x - rect.x
2310 title_bar_height = client_y - rect.y
2311
2312 if self.GetMenuBar():
2313 title_bar_height /= 2
2314 rect.width += (border_width * 2)
2315 rect.height += title_bar_height + border_width
2316
2317 wdc = wx.ScreenDC()
2318 mdc = wx.MemoryDC()
2319 img = wx.EmptyBitmap(rect.width, rect.height)
2320 mdc.SelectObject(img)
2321 mdc.Blit (
2322 0, 0,
2323 rect.width, rect.height,
2324 wdc,
2325 rect.x, rect.y
2326 )
2327
2328
2329 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2330 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2331 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2332
2334
2335 raise ValueError('raised ValueError to test exception handling')
2336
2338 import wx.lib.inspection
2339 wx.lib.inspection.InspectionTool().Show()
2340
2343
2346
2349
2352
2359
2363
2366
2373
2378
2380 name = os.path.basename(gmLog2._logfile_name)
2381 name, ext = os.path.splitext(name)
2382 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2383 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2384
2385 dlg = wx.FileDialog (
2386 parent = self,
2387 message = _("Save current log as..."),
2388 defaultDir = new_path,
2389 defaultFile = new_name,
2390 wildcard = "%s (*.log)|*.log" % _("log files"),
2391 style = wx.SAVE
2392 )
2393 choice = dlg.ShowModal()
2394 new_name = dlg.GetPath()
2395 dlg.Destroy()
2396 if choice != wx.ID_OK:
2397 return True
2398
2399 _log.warning('syncing log file for backup to [%s]', new_name)
2400 gmLog2.flush()
2401 shutil.copy2(gmLog2._logfile_name, new_name)
2402 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2403
2406
2407
2408
2410 """This is the wx.EVT_CLOSE handler.
2411
2412 - framework still functional
2413 """
2414 _log.debug('gmTopLevelFrame.OnClose() start')
2415 self._clean_exit()
2416 self.Destroy()
2417 _log.debug('gmTopLevelFrame.OnClose() end')
2418 return True
2419
2425
2430
2438
2445
2452
2459
2469
2477
2485
2493
2501
2510
2519
2527
2544
2547
2550
2552
2553 pat = gmPerson.gmCurrentPatient()
2554 if not pat.connected:
2555 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2556 return False
2557
2558 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2559
2560 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2561 gmTools.mkdir(aDefDir)
2562
2563 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2564 dlg = wx.FileDialog (
2565 parent = self,
2566 message = _("Save patient's EMR journal as..."),
2567 defaultDir = aDefDir,
2568 defaultFile = fname,
2569 wildcard = aWildcard,
2570 style = wx.SAVE
2571 )
2572 choice = dlg.ShowModal()
2573 fname = dlg.GetPath()
2574 dlg.Destroy()
2575 if choice != wx.ID_OK:
2576 return True
2577
2578 _log.debug('exporting EMR journal to [%s]' % fname)
2579
2580 exporter = gmPatientExporter.cEMRJournalExporter()
2581
2582 wx.BeginBusyCursor()
2583 try:
2584 fname = exporter.export_to_file(filename = fname)
2585 except:
2586 wx.EndBusyCursor()
2587 gmGuiHelpers.gm_show_error (
2588 _('Error exporting patient EMR as chronological journal.'),
2589 _('EMR journal export')
2590 )
2591 raise
2592 wx.EndBusyCursor()
2593
2594 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2595
2596 return True
2597
2604
2606 curr_pat = gmPerson.gmCurrentPatient()
2607 if not curr_pat.connected:
2608 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2609 return
2610
2611 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2612 if tag is None:
2613 return
2614
2615 tag = curr_pat.add_tag(tag['pk_tag_image'])
2616 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2617 comment = wx.GetTextFromUser (
2618 message = msg,
2619 caption = _('Editing tag comment'),
2620 default_value = gmTools.coalesce(tag['comment'], u''),
2621 parent = self
2622 )
2623
2624 if comment == u'':
2625 return
2626
2627 if comment.strip() == tag['comment']:
2628 return
2629
2630 if comment == u' ':
2631 tag['comment'] = None
2632 else:
2633 tag['comment'] = comment.strip()
2634
2635 tag.save()
2636
2646
2648 curr_pat = gmPerson.gmCurrentPatient()
2649 if not curr_pat.connected:
2650 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2651 return False
2652
2653 enc = 'cp850'
2654 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2655 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2656 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2657
2660
2668
2676
2679
2686
2690
2693
2694
2695
2696
2699
2702
2707
2709 """Cleanup helper.
2710
2711 - should ALWAYS be called when this program is
2712 to be terminated
2713 - ANY code that should be executed before a
2714 regular shutdown should go in here
2715 - framework still functional
2716 """
2717 _log.debug('gmTopLevelFrame._clean_exit() start')
2718
2719
2720 listener = gmBackendListener.gmBackendListener()
2721 try:
2722 listener.shutdown()
2723 except:
2724 _log.exception('cannot stop backend notifications listener thread')
2725
2726
2727 if _scripting_listener is not None:
2728 try:
2729 _scripting_listener.shutdown()
2730 except:
2731 _log.exception('cannot stop scripting listener thread')
2732
2733
2734 self.clock_update_timer.Stop()
2735 gmTimer.shutdown()
2736 gmPhraseWheel.shutdown()
2737
2738
2739 for call_back in self.__pre_exit_callbacks:
2740 try:
2741 call_back()
2742 except:
2743 print "*** pre-exit callback failed ***"
2744 print call_back
2745 _log.exception('callback [%s] failed', call_back)
2746
2747
2748 gmDispatcher.send(u'application_closing')
2749
2750
2751 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2752
2753
2754 curr_width, curr_height = self.GetClientSizeTuple()
2755 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2756 dbcfg = gmCfg.cCfgSQL()
2757 dbcfg.set (
2758 option = 'main.window.width',
2759 value = curr_width,
2760 workplace = gmSurgery.gmCurrentPractice().active_workplace
2761 )
2762 dbcfg.set (
2763 option = 'main.window.height',
2764 value = curr_height,
2765 workplace = gmSurgery.gmCurrentPractice().active_workplace
2766 )
2767
2768 if _cfg.get(option = 'debug'):
2769 print '---=== GNUmed shutdown ===---'
2770 try:
2771 print _('You have to manually close this window to finalize shutting down GNUmed.')
2772 print _('This is so that you can inspect the console output at your leisure.')
2773 except UnicodeEncodeError:
2774 print 'You have to manually close this window to finalize shutting down GNUmed.'
2775 print 'This is so that you can inspect the console output at your leisure.'
2776 print '---=== GNUmed shutdown ===---'
2777
2778
2779 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2780
2781
2782 import threading
2783 _log.debug("%s active threads", threading.activeCount())
2784 for t in threading.enumerate():
2785 _log.debug('thread %s', t)
2786
2787 _log.debug('gmTopLevelFrame._clean_exit() end')
2788
2789
2790
2792
2793 if _cfg.get(option = 'slave'):
2794 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2795 _cfg.get(option = 'slave personality'),
2796 _cfg.get(option = 'xml-rpc port')
2797 )
2798 else:
2799 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2800
2802 """Update title of main window based on template.
2803
2804 This gives nice tooltips on iconified GNUmed instances.
2805
2806 User research indicates that in the title bar people want
2807 the date of birth, not the age, so please stick to this
2808 convention.
2809 """
2810 args = {}
2811
2812 pat = gmPerson.gmCurrentPatient()
2813 if pat.connected:
2814 args['pat'] = u'%s %s %s (%s) #%d' % (
2815 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2816 pat['firstnames'],
2817 pat['lastnames'],
2818 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2819 pat['pk_identity']
2820 )
2821 else:
2822 args['pat'] = _('no patient')
2823
2824 args['prov'] = u'%s%s.%s' % (
2825 gmTools.coalesce(_provider['title'], u'', u'%s '),
2826 _provider['firstnames'][:1],
2827 _provider['lastnames']
2828 )
2829
2830 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2831
2832 self.SetTitle(self.__title_template % args)
2833
2834
2836 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2837 sb.SetStatusWidths([-1, 225])
2838
2839 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2840 self._cb_update_clock()
2841
2842 self.clock_update_timer.Start(milliseconds = 1000)
2843
2845 """Displays date and local time in the second slot of the status bar"""
2846 t = time.localtime(time.time())
2847 st = time.strftime('%c', t).decode(gmI18N.get_encoding(), 'replace')
2848 self.SetStatusText(st, 1)
2849
2851 """Lock GNUmed client against unauthorized access"""
2852
2853
2854
2855 return
2856
2858 """Unlock the main notebook widgets
2859 As long as we are not logged into the database backend,
2860 all pages but the 'login' page of the main notebook widget
2861 are locked; i.e. not accessible by the user
2862 """
2863
2864
2865
2866
2867
2868 return
2869
2871 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2872
2874
2876
2877 self.__starting_up = True
2878
2879 gmExceptionHandlingWidgets.install_wx_exception_handler()
2880 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2881
2882
2883
2884
2885 self.SetAppName(u'gnumed')
2886 self.SetVendorName(u'The GNUmed Development Community.')
2887 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2888 paths.init_paths(wx = wx, app_name = u'gnumed')
2889
2890 if not self.__setup_prefs_file():
2891 return False
2892
2893 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2894
2895 self.__guibroker = gmGuiBroker.GuiBroker()
2896 self.__setup_platform()
2897
2898 if not self.__establish_backend_connection():
2899 return False
2900
2901 if not _cfg.get(option = 'skip-update-check'):
2902 self.__check_for_updates()
2903
2904 if _cfg.get(option = 'slave'):
2905 if not self.__setup_scripting_listener():
2906 return False
2907
2908
2909 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2910 frame.CentreOnScreen(wx.BOTH)
2911 self.SetTopWindow(frame)
2912 frame.Show(True)
2913
2914 if _cfg.get(option = 'debug'):
2915 self.RedirectStdio()
2916 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2917
2918
2919 print '---=== GNUmed startup ===---'
2920 print _('redirecting STDOUT/STDERR to this log window')
2921 print '---=== GNUmed startup ===---'
2922
2923 self.__setup_user_activity_timer()
2924 self.__register_events()
2925
2926 wx.CallAfter(self._do_after_init)
2927
2928 return True
2929
2931 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2932
2933 - after destroying all application windows and controls
2934 - before wx.Windows internal cleanup
2935 """
2936 _log.debug('gmApp.OnExit() start')
2937
2938 self.__shutdown_user_activity_timer()
2939
2940 if _cfg.get(option = 'debug'):
2941 self.RestoreStdio()
2942 sys.stdin = sys.__stdin__
2943 sys.stdout = sys.__stdout__
2944 sys.stderr = sys.__stderr__
2945
2946 _log.debug('gmApp.OnExit() end')
2947
2949 wx.Bell()
2950 wx.Bell()
2951 wx.Bell()
2952 _log.warning('unhandled event detected: QUERY_END_SESSION')
2953 _log.info('we should be saving ourselves from here')
2954 gmLog2.flush()
2955 print "unhandled event detected: QUERY_END_SESSION"
2956
2958 wx.Bell()
2959 wx.Bell()
2960 wx.Bell()
2961 _log.warning('unhandled event detected: END_SESSION')
2962 gmLog2.flush()
2963 print "unhandled event detected: END_SESSION"
2964
2975
2977 self.user_activity_detected = True
2978 evt.Skip()
2979
2981
2982 if self.user_activity_detected:
2983 self.elapsed_inactivity_slices = 0
2984 self.user_activity_detected = False
2985 self.elapsed_inactivity_slices += 1
2986 else:
2987 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2988
2989 pass
2990
2991 self.user_activity_timer.Start(oneShot = True)
2992
2993
2994
2996 try:
2997 kwargs['originated_in_database']
2998 print '==> got notification from database "%s":' % kwargs['signal']
2999 except KeyError:
3000 print '==> received signal from client: "%s"' % kwargs['signal']
3001
3002 del kwargs['signal']
3003 for key in kwargs.keys():
3004 print ' [%s]: %s' % (key, kwargs[key])
3005
3011
3013 self.user_activity_detected = True
3014 self.elapsed_inactivity_slices = 0
3015
3016 self.max_user_inactivity_slices = 15
3017 self.user_activity_timer = gmTimer.cTimer (
3018 callback = self._on_user_activity_timer_expired,
3019 delay = 2000
3020 )
3021 self.user_activity_timer.Start(oneShot=True)
3022
3024 try:
3025 self.user_activity_timer.Stop()
3026 del self.user_activity_timer
3027 except:
3028 pass
3029
3031 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
3032 wx.EVT_END_SESSION(self, self._on_end_session)
3033
3034
3035
3036
3037
3038 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
3039
3040 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
3041 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
3042
3043 if _cfg.get(option = 'debug'):
3044 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
3045 _log.debug('connected signal monitor')
3046
3062
3064 """Handle all the database related tasks necessary for startup."""
3065
3066
3067 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
3068
3069 from Gnumed.wxpython import gmAuthWidgets
3070 connected = gmAuthWidgets.connect_to_database (
3071 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
3072 require_version = not override
3073 )
3074 if not connected:
3075 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
3076 return False
3077
3078
3079 try:
3080 global _provider
3081 _provider = gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
3082 except ValueError:
3083 account = gmPG2.get_current_user()
3084 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
3085 msg = _(
3086 'The database account [%s] cannot be used as a\n'
3087 'staff member login for GNUmed. There was an\n'
3088 'error retrieving staff details for it.\n\n'
3089 'Please ask your administrator for help.\n'
3090 ) % account
3091 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
3092 return False
3093
3094
3095 tmp = '%s%s %s (%s = %s)' % (
3096 gmTools.coalesce(_provider['title'], ''),
3097 _provider['firstnames'],
3098 _provider['lastnames'],
3099 _provider['short_alias'],
3100 _provider['db_user']
3101 )
3102 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
3103
3104
3105 surgery = gmSurgery.gmCurrentPractice()
3106 msg = surgery.db_logon_banner
3107 if msg.strip() != u'':
3108
3109 login = gmPG2.get_default_login()
3110 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
3111 login.database,
3112 gmTools.coalesce(login.host, u'localhost')
3113 ))
3114 msg = auth + msg + u'\n\n'
3115
3116 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3117 None,
3118
3119 -1,
3120 caption = _('Verifying database'),
3121 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3122 button_defs = [
3123 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3124 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3125 ]
3126 )
3127 go_on = dlg.ShowModal()
3128 dlg.Destroy()
3129 if go_on != wx.ID_YES:
3130 _log.info('user decided to not connect to this database')
3131 return False
3132
3133
3134 self.__check_db_lang()
3135
3136 return True
3137
3139 """Setup access to a config file for storing preferences."""
3140
3141 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3142
3143 candidates = []
3144 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3145 if explicit_file is not None:
3146 candidates.append(explicit_file)
3147
3148 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3149 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3150 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3151
3152 prefs_file = None
3153 for candidate in candidates:
3154 try:
3155 open(candidate, 'a+').close()
3156 prefs_file = candidate
3157 break
3158 except IOError:
3159 continue
3160
3161 if prefs_file is None:
3162 msg = _(
3163 'Cannot find configuration file in any of:\n'
3164 '\n'
3165 ' %s\n'
3166 'You may need to use the comand line option\n'
3167 '\n'
3168 ' --conf-file=<FILE>'
3169 ) % '\n '.join(candidates)
3170 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3171 return False
3172
3173 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3174 _log.info('user preferences file: %s', prefs_file)
3175
3176 return True
3177
3179
3180 from socket import error as SocketError
3181 from Gnumed.pycommon import gmScriptingListener
3182 from Gnumed.wxpython import gmMacro
3183
3184 slave_personality = gmTools.coalesce (
3185 _cfg.get (
3186 group = u'workplace',
3187 option = u'slave personality',
3188 source_order = [
3189 ('explicit', 'return'),
3190 ('workbase', 'return'),
3191 ('user', 'return'),
3192 ('system', 'return')
3193 ]
3194 ),
3195 u'gnumed-client'
3196 )
3197 _cfg.set_option(option = 'slave personality', value = slave_personality)
3198
3199
3200 port = int (
3201 gmTools.coalesce (
3202 _cfg.get (
3203 group = u'workplace',
3204 option = u'xml-rpc port',
3205 source_order = [
3206 ('explicit', 'return'),
3207 ('workbase', 'return'),
3208 ('user', 'return'),
3209 ('system', 'return')
3210 ]
3211 ),
3212 9999
3213 )
3214 )
3215 _cfg.set_option(option = 'xml-rpc port', value = port)
3216
3217 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3218 global _scripting_listener
3219 try:
3220 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3221 except SocketError, e:
3222 _log.exception('cannot start GNUmed XML-RPC server')
3223 gmGuiHelpers.gm_show_error (
3224 aMessage = (
3225 'Cannot start the GNUmed server:\n'
3226 '\n'
3227 ' [%s]'
3228 ) % e,
3229 aTitle = _('GNUmed startup')
3230 )
3231 return False
3232
3233 return True
3234
3255
3257 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3258 _log.warning("system locale is undefined (probably meaning 'C')")
3259 return True
3260
3261
3262 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3263 db_lang = rows[0]['lang']
3264
3265 if db_lang is None:
3266 _log.debug("database locale currently not set")
3267 msg = _(
3268 "There is no language selected in the database for user [%s].\n"
3269 "Your system language is currently set to [%s].\n\n"
3270 "Do you want to set the database language to '%s' ?\n\n"
3271 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3272 checkbox_msg = _('Remember to ignore missing language')
3273 else:
3274 _log.debug("current database locale: [%s]" % db_lang)
3275 msg = _(
3276 "The currently selected database language ('%s') does\n"
3277 "not match the current system language ('%s').\n"
3278 "\n"
3279 "Do you want to set the database language to '%s' ?\n"
3280 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3281 checkbox_msg = _('Remember to ignore language mismatch')
3282
3283
3284 if db_lang == gmI18N.system_locale_level['full']:
3285 _log.debug('Database locale (%s) up to date.' % db_lang)
3286 return True
3287 if db_lang == gmI18N.system_locale_level['country']:
3288 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3289 return True
3290 if db_lang == gmI18N.system_locale_level['language']:
3291 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3292 return True
3293
3294 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3295
3296
3297 ignored_sys_lang = _cfg.get (
3298 group = u'backend',
3299 option = u'ignored mismatching system locale',
3300 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3301 )
3302
3303
3304 if gmI18N.system_locale == ignored_sys_lang:
3305 _log.info('configured to ignore system-to-database locale mismatch')
3306 return True
3307
3308
3309 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3310 None,
3311 -1,
3312 caption = _('Checking database language settings'),
3313 question = msg,
3314 button_defs = [
3315 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3316 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3317 ],
3318 show_checkbox = True,
3319 checkbox_msg = checkbox_msg,
3320 checkbox_tooltip = _(
3321 'Checking this will make GNUmed remember your decision\n'
3322 'until the system language is changed.\n'
3323 '\n'
3324 'You can also reactivate this inquiry by removing the\n'
3325 'corresponding "ignore" option from the configuration file\n'
3326 '\n'
3327 ' [%s]'
3328 ) % _cfg.get(option = 'user_preferences_file')
3329 )
3330 decision = dlg.ShowModal()
3331 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3332 dlg.Destroy()
3333
3334 if decision == wx.ID_NO:
3335 if not remember_ignoring_problem:
3336 return True
3337 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3338 gmCfg2.set_option_in_INI_file (
3339 filename = _cfg.get(option = 'user_preferences_file'),
3340 group = 'backend',
3341 option = 'ignored mismatching system locale',
3342 value = gmI18N.system_locale
3343 )
3344 return True
3345
3346
3347 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3348 if len(lang) > 0:
3349
3350
3351 rows, idx = gmPG2.run_rw_queries (
3352 link_obj = None,
3353 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3354 return_data = True
3355 )
3356 if rows[0][0]:
3357 _log.debug("Successfully set database language to [%s]." % lang)
3358 else:
3359 _log.error('Cannot set database language to [%s].' % lang)
3360 continue
3361 return True
3362
3363
3364 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3365 gmPG2.run_rw_queries(queries = [{
3366 'cmd': u'select i18n.force_curr_lang(%s)',
3367 'args': [gmI18N.system_locale_level['country']]
3368 }])
3369
3370 return True
3371
3373 try:
3374 kwargs['originated_in_database']
3375 print '==> got notification from database "%s":' % kwargs['signal']
3376 except KeyError:
3377 print '==> received signal from client: "%s"' % kwargs['signal']
3378
3379 del kwargs['signal']
3380 for key in kwargs.keys():
3381
3382 try: print ' [%s]: %s' % (key, kwargs[key])
3383 except: print 'cannot print signal information'
3384
3388
3399
3401
3402 if _cfg.get(option = 'debug'):
3403 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3404 _log.debug('gmDispatcher signal monitor activated')
3405
3406 setup_safe_wxEndBusyCursor()
3407
3408 wx.InitAllImageHandlers()
3409
3410
3411
3412 app = gmApp(redirect = False, clearSigInt = False)
3413 app.MainLoop()
3414
3415
3416
3417 if __name__ == '__main__':
3418
3419 from GNUmed.pycommon import gmI18N
3420 gmI18N.activate_locale()
3421 gmI18N.install_domain()
3422
3423 _log.info('Starting up as main module.')
3424 main()
3425
3426
3427