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 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
488 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
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 item = help_menu.Append(-1, _('&Clear status line'), _('Clear out the status line.'))
766 self.Bind(wx.EVT_MENU, self.__on_clear_status_line, item)
767
768 menu_debugging = wx.Menu()
769
770 ID_SCREENSHOT = wx.NewId()
771 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
772 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
773
774 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
775 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
776
777 ID = wx.NewId()
778 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
779 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
780
781 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
782 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
783
784 ID = wx.NewId()
785 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
786 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
787
788 ID_UNBLOCK = wx.NewId()
789 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
790 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
791
792 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
793 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
794
795
796
797
798 if _cfg.get(option = 'debug'):
799 ID_TOGGLE_PAT_LOCK = wx.NewId()
800 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
801 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
802
803 ID_TEST_EXCEPTION = wx.NewId()
804 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
805 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
806
807 ID = wx.NewId()
808 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
809 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
810 try:
811 import wx.lib.inspection
812 except ImportError:
813 menu_debugging.Enable(id = ID, enable = False)
814
815 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
816
817 help_menu.AppendSeparator()
818
819 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
820 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
821
822 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
823 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
824
825 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors'))
826 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item)
827
828 help_menu.AppendSeparator()
829
830 self.mainmenu.Append(help_menu, _("&Help"))
831
832 self.__gb['main.helpmenu'] = help_menu
833
834
835 self.SetMenuBar(self.mainmenu)
836
839
840
841
843 """register events we want to react to"""
844
845 wx.EVT_CLOSE(self, self.OnClose)
846 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
847 wx.EVT_END_SESSION(self, self._on_end_session)
848
849 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
850 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
851 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
852 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
853 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
854 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
855 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
856 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
857
858 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
859
860 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
861
862 _log.debug('registering plugin with menu system')
863 _log.debug(' generic name: %s', plugin_name)
864 _log.debug(' class name: %s', class_name)
865 _log.debug(' specific menu: %s', menu_name)
866 _log.debug(' menu item: %s', menu_item_name)
867
868
869 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
870 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
871 self.menu_id2plugin[item.Id] = class_name
872
873
874 if menu_name is not None:
875 menu = self.__gb['main.%smenu' % menu_name]
876 item = menu.Append(-1, menu_item_name, menu_help_string)
877 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
878 self.menu_id2plugin[item.Id] = class_name
879
880 return True
881
883 gmDispatcher.send (
884 signal = u'display_widget',
885 name = self.menu_id2plugin[evt.Id]
886 )
887
889 wx.Bell()
890 wx.Bell()
891 wx.Bell()
892 _log.warning('unhandled event detected: QUERY_END_SESSION')
893 _log.info('we should be saving ourselves from here')
894 gmLog2.flush()
895 print "unhandled event detected: QUERY_END_SESSION"
896
898 wx.Bell()
899 wx.Bell()
900 wx.Bell()
901 _log.warning('unhandled event detected: END_SESSION')
902 gmLog2.flush()
903 print "unhandled event detected: END_SESSION"
904
906 if not callable(callback):
907 raise TypeError(u'callback [%s] not callable' % callback)
908
909 self.__pre_exit_callbacks.append(callback)
910
911 - def _on_set_statustext_pubsub(self, context=None):
912 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
913 wx.CallAfter(self.SetStatusText, msg)
914
915 try:
916 if context.data['beep']:
917 wx.Bell()
918 except KeyError:
919 pass
920
921 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
922
923 if msg is None:
924 msg = _('programmer forgot to specify status message')
925
926 if loglevel is not None:
927 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
928
929 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
930 wx.CallAfter(self.SetStatusText, msg)
931
932 if beep:
933 wx.Bell()
934
936 wx.CallAfter(self.__on_db_maintenance_warning)
937
939
940 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
941 wx.Bell()
942 if not wx.GetApp().IsActive():
943 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
944
945 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
946
947 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
948 None,
949 -1,
950 caption = _('Database shutdown warning'),
951 question = _(
952 'The database will be shut down for maintenance\n'
953 'in a few minutes.\n'
954 '\n'
955 'In order to not suffer any loss of data you\n'
956 'will need to save your current work and log\n'
957 'out of this GNUmed client.\n'
958 ),
959 button_defs = [
960 {
961 u'label': _('Close now'),
962 u'tooltip': _('Close this GNUmed client immediately.'),
963 u'default': False
964 },
965 {
966 u'label': _('Finish work'),
967 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
968 u'default': True
969 }
970 ]
971 )
972 decision = dlg.ShowModal()
973 if decision == wx.ID_YES:
974 top_win = wx.GetApp().GetTopWindow()
975 wx.CallAfter(top_win.Close)
976
978 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
979
981
982 if not wx.GetApp().IsActive():
983 if urgent:
984 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
985 else:
986 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
987
988 if msg is not None:
989 self.SetStatusText(msg)
990
991 if urgent:
992 wx.Bell()
993
994 gmHooks.run_hook_script(hook = u'request_user_attention')
995
997 wx.CallAfter(self.__on_pat_name_changed)
998
1000 self.__update_window_title()
1001
1002 - def _on_post_patient_selection(self, **kwargs):
1003 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
1004
1006 self.__update_window_title()
1007 gmDispatcher.send(signal = 'statustext', msg = u'')
1008 try:
1009 gmHooks.run_hook_script(hook = u'post_patient_activation')
1010 except:
1011 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
1012 raise
1013
1015 return self.__sanity_check_encounter()
1016
1078
1079
1080
1083
1090
1091
1092
1108
1131
1133 from Gnumed.wxpython import gmAbout
1134 contribs = gmAbout.cContributorsDlg (
1135 parent = self,
1136 id = -1,
1137 title = _('GNUmed contributors'),
1138 size = wx.Size(400,600),
1139 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1140 )
1141 contribs.ShowModal()
1142 del contribs
1143 del gmAbout
1144
1145
1146
1148 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1149 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1150 self.Close(True)
1151 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1152
1155
1157 send = gmGuiHelpers.gm_show_question (
1158 _('This will send a notification about database downtime\n'
1159 'to all GNUmed clients connected to your database.\n'
1160 '\n'
1161 'Do you want to send the notification ?\n'
1162 ),
1163 _('Announcing database maintenance downtime')
1164 )
1165 if not send:
1166 return
1167 gmPG2.send_maintenance_notification()
1168
1169
1172
1173
1174
1187
1188 gmCfgWidgets.configure_string_option (
1189 message = _(
1190 'Some network installations cannot cope with loading\n'
1191 'documents of arbitrary size in one piece from the\n'
1192 'database (mainly observed on older Windows versions)\n.'
1193 '\n'
1194 'Under such circumstances documents need to be retrieved\n'
1195 'in chunks and reassembled on the client.\n'
1196 '\n'
1197 'Here you can set the size (in Bytes) above which\n'
1198 'GNUmed will retrieve documents in chunks. Setting this\n'
1199 'value to 0 will disable the chunking protocol.'
1200 ),
1201 option = 'horstspace.blob_export_chunk_size',
1202 bias = 'workplace',
1203 default_value = 1024 * 1024,
1204 validator = is_valid
1205 )
1206
1207
1208
1276
1280
1281
1282
1291
1292 gmCfgWidgets.configure_string_option (
1293 message = _(
1294 'When GNUmed cannot find an OpenOffice server it\n'
1295 'will try to start one. OpenOffice, however, needs\n'
1296 'some time to fully start up.\n'
1297 '\n'
1298 'Here you can set the time for GNUmed to wait for OOo.\n'
1299 ),
1300 option = 'external.ooo.startup_settle_time',
1301 bias = 'workplace',
1302 default_value = 2.0,
1303 validator = is_valid
1304 )
1305
1308
1323
1324 gmCfgWidgets.configure_string_option (
1325 message = _(
1326 'GNUmed will use this URL to access a website which lets\n'
1327 'you report an adverse drug reaction (ADR).\n'
1328 '\n'
1329 'If you leave this empty it will fall back\n'
1330 'to an URL for reporting ADRs in Germany.'
1331 ),
1332 option = 'external.urls.report_ADR',
1333 bias = 'user',
1334 default_value = german_default,
1335 validator = is_valid
1336 )
1337
1351
1352 gmCfgWidgets.configure_string_option (
1353 message = _(
1354 'GNUmed will use this URL to access a website which lets\n'
1355 'you report an adverse vaccination reaction (vADR).\n'
1356 '\n'
1357 'If you set it to a specific address that URL must be\n'
1358 'accessible now. If you leave it empty it will fall back\n'
1359 'to the URL for reporting other adverse drug reactions.'
1360 ),
1361 option = 'external.urls.report_vaccine_ADR',
1362 bias = 'user',
1363 default_value = german_default,
1364 validator = is_valid
1365 )
1366
1380
1381 gmCfgWidgets.configure_string_option (
1382 message = _(
1383 'GNUmed will use this URL to access an encyclopedia of\n'
1384 'measurement/lab methods from within the measurments grid.\n'
1385 '\n'
1386 'You can leave this empty but to set it to a specific\n'
1387 'address the URL must be accessible now.'
1388 ),
1389 option = 'external.urls.measurements_encyclopedia',
1390 bias = 'user',
1391 default_value = german_default,
1392 validator = is_valid
1393 )
1394
1408
1409 gmCfgWidgets.configure_string_option (
1410 message = _(
1411 'GNUmed will use this URL to access a page showing\n'
1412 'vaccination schedules.\n'
1413 '\n'
1414 'You can leave this empty but to set it to a specific\n'
1415 'address the URL must be accessible now.'
1416 ),
1417 option = 'external.urls.vaccination_plans',
1418 bias = 'user',
1419 default_value = german_default,
1420 validator = is_valid
1421 )
1422
1435
1436 gmCfgWidgets.configure_string_option (
1437 message = _(
1438 'Enter the shell command with which to start the\n'
1439 'the ACS risk assessment calculator.\n'
1440 '\n'
1441 'GNUmed will try to verify the path which may,\n'
1442 'however, fail if you are using an emulator such\n'
1443 'as Wine. Nevertheless, starting the calculator\n'
1444 'will work as long as the shell command is correct\n'
1445 'despite the failing test.'
1446 ),
1447 option = 'external.tools.acs_risk_calculator_cmd',
1448 bias = 'user',
1449 validator = is_valid
1450 )
1451
1454
1467
1468 gmCfgWidgets.configure_string_option (
1469 message = _(
1470 'Enter the shell command with which to start\n'
1471 'the FreeDiams drug database frontend.\n'
1472 '\n'
1473 'GNUmed will try to verify that path.'
1474 ),
1475 option = 'external.tools.freediams_cmd',
1476 bias = 'workplace',
1477 default_value = None,
1478 validator = is_valid
1479 )
1480
1493
1494 gmCfgWidgets.configure_string_option (
1495 message = _(
1496 'Enter the shell command with which to start the\n'
1497 'the IFAP drug database.\n'
1498 '\n'
1499 'GNUmed will try to verify the path which may,\n'
1500 'however, fail if you are using an emulator such\n'
1501 'as Wine. Nevertheless, starting IFAP will work\n'
1502 'as long as the shell command is correct despite\n'
1503 'the failing test.'
1504 ),
1505 option = 'external.ifap-win.shell_command',
1506 bias = 'workplace',
1507 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1508 validator = is_valid
1509 )
1510
1511
1512
1561
1562
1563
1580
1583
1586
1591
1592 gmCfgWidgets.configure_string_option (
1593 message = _(
1594 'When a patient is activated GNUmed checks the\n'
1595 "proximity of the patient's birthday.\n"
1596 '\n'
1597 'If the birthday falls within the range of\n'
1598 ' "today %s <the interval you set here>"\n'
1599 'GNUmed will remind you of the recent or\n'
1600 'imminent anniversary.'
1601 ) % u'\u2213',
1602 option = u'patient_search.dob_warn_interval',
1603 bias = 'user',
1604 default_value = '1 week',
1605 validator = is_valid
1606 )
1607
1609
1610 gmCfgWidgets.configure_boolean_option (
1611 parent = self,
1612 question = _(
1613 'When adding progress notes do you want to\n'
1614 'allow opening several unassociated, new\n'
1615 'episodes for a patient at once ?\n'
1616 '\n'
1617 'This can be particularly helpful when entering\n'
1618 'progress notes on entirely new patients presenting\n'
1619 'with a multitude of problems on their first visit.'
1620 ),
1621 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1622 button_tooltips = [
1623 _('Yes, allow for multiple new episodes concurrently.'),
1624 _('No, only allow editing one new episode at a time.')
1625 ]
1626 )
1627
1629
1630 gmCfgWidgets.configure_boolean_option (
1631 parent = self,
1632 question = _(
1633 'When activating a patient, do you want GNUmed to\n'
1634 'auto-open editors for all active problems that were\n'
1635 'touched upon during the current and the most recent\n'
1636 'encounter ?'
1637 ),
1638 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1639 button_tooltips = [
1640 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1641 _('No, only auto-open one editor for a new, unassociated problem.')
1642 ]
1643 )
1644
1690
1691
1692
1695
1698
1711
1712 gmCfgWidgets.configure_string_option (
1713 message = _(
1714 'GNUmed will use this URL to let you browse\n'
1715 'billing catalogs (schedules of fees).\n'
1716 '\n'
1717 'You can leave this empty but to set it to a specific\n'
1718 'address the URL must be accessible now.'
1719 ),
1720 option = 'external.urls.schedules_of_fees',
1721 bias = 'user',
1722 default_value = german_default,
1723 validator = is_valid
1724 )
1725
1726
1727
1730
1733
1747
1749 gmCfgWidgets.configure_boolean_option (
1750 parent = self,
1751 question = _(
1752 'Do you want GNUmed to show the encounter\n'
1753 'details editor when changing the active patient ?'
1754 ),
1755 option = 'encounter.show_editor_before_patient_change',
1756 button_tooltips = [
1757 _('Yes, show the encounter editor if it seems appropriate.'),
1758 _('No, never show the encounter editor even if it would seem useful.')
1759 ]
1760 )
1761
1766
1767 gmCfgWidgets.configure_string_option (
1768 message = _(
1769 'When a patient is activated GNUmed checks the\n'
1770 'chart for encounters lacking any entries.\n'
1771 '\n'
1772 'Any such encounters older than what you set\n'
1773 'here will be removed from the medical record.\n'
1774 '\n'
1775 'To effectively disable removal of such encounters\n'
1776 'set this option to an improbable value.\n'
1777 ),
1778 option = 'encounter.ttl_if_empty',
1779 bias = 'user',
1780 default_value = '1 week',
1781 validator = is_valid
1782 )
1783
1788
1789 gmCfgWidgets.configure_string_option (
1790 message = _(
1791 'When a patient is activated GNUmed checks the\n'
1792 'age of the most recent encounter.\n'
1793 '\n'
1794 'If that encounter is younger than this age\n'
1795 'the existing encounter will be continued.\n'
1796 '\n'
1797 '(If it is really old a new encounter is\n'
1798 ' started, or else GNUmed will ask you.)\n'
1799 ),
1800 option = 'encounter.minimum_ttl',
1801 bias = 'user',
1802 default_value = '1 hour 30 minutes',
1803 validator = is_valid
1804 )
1805
1810
1811 gmCfgWidgets.configure_string_option (
1812 message = _(
1813 'When a patient is activated GNUmed checks the\n'
1814 'age of the most recent encounter.\n'
1815 '\n'
1816 'If that encounter is older than this age\n'
1817 'GNUmed will always start a new encounter.\n'
1818 '\n'
1819 '(If it is very recent the existing encounter\n'
1820 ' is continued, or else GNUmed will ask you.)\n'
1821 ),
1822 option = 'encounter.maximum_ttl',
1823 bias = 'user',
1824 default_value = '6 hours',
1825 validator = is_valid
1826 )
1827
1836
1837 gmCfgWidgets.configure_string_option (
1838 message = _(
1839 'At any time there can only be one open (ongoing)\n'
1840 'episode for each health issue.\n'
1841 '\n'
1842 'When you try to open (add data to) an episode on a health\n'
1843 'issue GNUmed will check for an existing open episode on\n'
1844 'that issue. If there is any it will check the age of that\n'
1845 'episode. The episode is closed if it has been dormant (no\n'
1846 'data added, that is) for the period of time (in days) you\n'
1847 'set here.\n'
1848 '\n'
1849 "If the existing episode hasn't been dormant long enough\n"
1850 'GNUmed will consult you what to do.\n'
1851 '\n'
1852 'Enter maximum episode dormancy in DAYS:'
1853 ),
1854 option = 'episode.ttl',
1855 bias = 'user',
1856 default_value = 60,
1857 validator = is_valid
1858 )
1859
1890
1905
1930
1942
1943 gmCfgWidgets.configure_string_option (
1944 message = _(
1945 'GNUmed can check for new releases being available. To do\n'
1946 'so it needs to load version information from an URL.\n'
1947 '\n'
1948 'The default URL is:\n'
1949 '\n'
1950 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1951 '\n'
1952 'but you can configure any other URL locally. Note\n'
1953 'that you must enter the location as a valid URL.\n'
1954 'Depending on the URL the client will need online\n'
1955 'access when checking for updates.'
1956 ),
1957 option = u'horstspace.update.url',
1958 bias = u'workplace',
1959 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1960 validator = is_valid
1961 )
1962
1980
1997
2014
2025
2026 gmCfgWidgets.configure_string_option (
2027 message = _(
2028 'GNUmed can show the document review dialog after\n'
2029 'calling the appropriate viewer for that document.\n'
2030 '\n'
2031 'Select the conditions under which you want\n'
2032 'GNUmed to do so:\n'
2033 '\n'
2034 ' 0: never display the review dialog\n'
2035 ' 1: always display the dialog\n'
2036 ' 2: only if there is no previous review by me\n'
2037 ' 3: only if there is no previous review at all\n'
2038 ' 4: only if there is no review by the responsible reviewer\n'
2039 '\n'
2040 'Note that if a viewer is configured to not block\n'
2041 'GNUmed during document display the review dialog\n'
2042 'will actually appear in parallel to the viewer.'
2043 ),
2044 option = u'horstspace.document_viewer.review_after_display',
2045 bias = u'user',
2046 default_value = 3,
2047 validator = is_valid
2048 )
2049
2051
2052
2053 master_data_lists = [
2054 'adr',
2055 'billables',
2056 'drugs',
2057 'hints',
2058 'codes',
2059 'communication_channel_types',
2060 'substances_in_brands',
2061 'substances',
2062 'labs',
2063 'form_templates',
2064 'doc_types',
2065 'enc_types',
2066 'text_expansions',
2067 'meta_test_types',
2068 'orgs',
2069 'patient_tags',
2070 'provinces',
2071 'db_translations',
2072 'ref_data_sources',
2073 'test_types',
2074 'vacc_indications',
2075 'vaccines',
2076 'workplaces'
2077 ]
2078
2079 master_data_list_names = {
2080 'adr': _('Addresses (likely slow)'),
2081 'drugs': _('Branded drugs (as marketed)'),
2082 'hints': _('Clinical hints'),
2083 'codes': _('Codes and their respective terms'),
2084 'communication_channel_types': _('Communication channel types'),
2085 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2086 'labs': _('Diagnostic organizations (path labs, ...)'),
2087 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2088 'doc_types': _('Document types'),
2089 'enc_types': _('Encounter types'),
2090 'text_expansions': _('Keyword based text expansion macros'),
2091 'meta_test_types': _('Meta test/measurement types'),
2092 'orgs': _('Organizations with their units, addresses, and comm channels'),
2093 'patient_tags': _('Patient tags'),
2094 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2095 'db_translations': _('String translations in the database'),
2096 'test_types': _('Test/measurement types'),
2097 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2098 'vaccines': _('Vaccines'),
2099 'workplaces': _('Workplace profiles (which plugins to load)'),
2100 'substances': _('Consumable substances'),
2101 'billables': _('Billable items'),
2102 'ref_data_sources': _('Reference data sources')
2103 }
2104
2105 map_list2handler = {
2106 'form_templates': gmFormWidgets.manage_form_templates,
2107 'doc_types': gmDocumentWidgets.manage_document_types,
2108 'text_expansions': gmTextExpansionWidgets.configure_keyword_text_expansion,
2109 'db_translations': gmI18nWidgets.manage_translations,
2110 'codes': gmCodingWidgets.browse_coded_terms,
2111 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2112 'provinces': gmAddressWidgets.manage_provinces,
2113 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins,
2114 'drugs': gmMedicationWidgets.manage_branded_drugs,
2115 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2116 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2117 'test_types': gmMeasurementWidgets.manage_measurement_types,
2118 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2119 'vaccines': gmVaccWidgets.manage_vaccines,
2120 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2121 'orgs': gmOrganizationWidgets.manage_orgs,
2122 'adr': gmAddressWidgets.manage_addresses,
2123 'substances': gmMedicationWidgets.manage_consumable_substances,
2124 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2125 'communication_channel_types': gmContactWidgets.manage_comm_channel_types,
2126 'billables': gmBillingWidgets.manage_billables,
2127 'ref_data_sources': gmCodingWidgets.browse_data_sources,
2128 'hints': gmProviderInboxWidgets.browse_dynamic_hints,
2129 }
2130
2131
2132 def edit(item):
2133 try: map_list2handler[item](parent = self)
2134 except KeyError: pass
2135 return False
2136
2137
2138 gmListWidgets.get_choices_from_list (
2139 parent = self,
2140 caption = _('Master data management'),
2141 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2142 data = master_data_lists,
2143 columns = [_('Select the list you want to manage:')],
2144 edit_callback = edit,
2145 single_selection = True,
2146 ignore_OK_button = True
2147 )
2148
2150
2151 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2152 if found:
2153 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2154 return
2155
2156 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2157 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking = False)
2158 return
2159
2160 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2161 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2162 if found:
2163 gmShellAPI.run_command_in_shell(cmd, blocking = False)
2164 return
2165
2166 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2167
2169
2170 curr_pat = gmPerson.gmCurrentPatient()
2171
2172 arriba = gmArriba.cArriba()
2173 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2174 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2175 return
2176
2177
2178 if curr_pat is None:
2179 return
2180
2181 if arriba.pdf_result is None:
2182 return
2183
2184 doc = gmDocumentWidgets.save_file_as_new_document (
2185 parent = self,
2186 filename = arriba.pdf_result,
2187 document_type = _('risk assessment')
2188 )
2189
2190 try: os.remove(arriba.pdf_result)
2191 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2192
2193 if doc is None:
2194 return
2195
2196 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2197 doc.save()
2198
2199 try:
2200 open(arriba.xml_result).close()
2201 part = doc.add_part(file = arriba.xml_result)
2202 except StandardError:
2203 _log.exception('error accessing [%s]', arriba.xml_result)
2204 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2205
2206 if part is None:
2207 return
2208
2209 part['obj_comment'] = u'XML-Daten'
2210 part['filename'] = u'arriba-result.xml'
2211 part.save()
2212
2214
2215 dbcfg = gmCfg.cCfgSQL()
2216 cmd = dbcfg.get2 (
2217 option = u'external.tools.acs_risk_calculator_cmd',
2218 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2219 bias = 'user'
2220 )
2221
2222 if cmd is None:
2223 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2224 return
2225
2226 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
2227 try:
2228 subprocess.check_call (
2229 args = (cmd,),
2230 close_fds = True,
2231 cwd = cwd
2232 )
2233 except (OSError, ValueError, subprocess.CalledProcessError):
2234 _log.exception('there was a problem executing [%s]', cmd)
2235 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2236 return
2237
2238 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2239 for pdf in pdfs:
2240 try:
2241 open(pdf).close()
2242 except:
2243 _log.exception('error accessing [%s]', pdf)
2244 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2245 continue
2246
2247 doc = gmDocumentWidgets.save_file_as_new_document (
2248 parent = self,
2249 filename = pdf,
2250 document_type = u'risk assessment'
2251 )
2252
2253 try:
2254 os.remove(pdf)
2255 except StandardError:
2256 _log.exception('cannot remove [%s]', pdf)
2257
2258 if doc is None:
2259 continue
2260 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2261 doc.save()
2262
2263 return
2264
2266 dlg = gmSnellen.cSnellenCfgDlg()
2267 if dlg.ShowModal() != wx.ID_OK:
2268 return
2269
2270 frame = gmSnellen.cSnellenChart (
2271 width = dlg.vals[0],
2272 height = dlg.vals[1],
2273 alpha = dlg.vals[2],
2274 mirr = dlg.vals[3],
2275 parent = None
2276 )
2277 frame.CentreOnScreen(wx.BOTH)
2278
2279
2280 frame.Show(True)
2281
2282
2285
2288
2291
2292
2293
2297
2300
2301
2302
2304 wx.CallAfter(self.__save_screenshot)
2305 evt.Skip()
2306
2308
2309 time.sleep(0.5)
2310
2311 rect = self.GetRect()
2312
2313
2314 if sys.platform == 'linux2':
2315 client_x, client_y = self.ClientToScreen((0, 0))
2316 border_width = client_x - rect.x
2317 title_bar_height = client_y - rect.y
2318
2319 if self.GetMenuBar():
2320 title_bar_height /= 2
2321 rect.width += (border_width * 2)
2322 rect.height += title_bar_height + border_width
2323
2324 wdc = wx.ScreenDC()
2325 mdc = wx.MemoryDC()
2326 img = wx.EmptyBitmap(rect.width, rect.height)
2327 mdc.SelectObject(img)
2328 mdc.Blit (
2329 0, 0,
2330 rect.width, rect.height,
2331 wdc,
2332 rect.x, rect.y
2333 )
2334
2335
2336 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2337 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2338 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2339
2341
2342 raise ValueError('raised ValueError to test exception handling')
2343
2345 import wx.lib.inspection
2346 wx.lib.inspection.InspectionTool().Show()
2347
2350
2353
2356
2359
2366
2370
2373
2376
2383
2388
2390 name = os.path.basename(gmLog2._logfile_name)
2391 name, ext = os.path.splitext(name)
2392 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2393 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2394
2395 dlg = wx.FileDialog (
2396 parent = self,
2397 message = _("Save current log as..."),
2398 defaultDir = new_path,
2399 defaultFile = new_name,
2400 wildcard = "%s (*.log)|*.log" % _("log files"),
2401 style = wx.SAVE
2402 )
2403 choice = dlg.ShowModal()
2404 new_name = dlg.GetPath()
2405 dlg.Destroy()
2406 if choice != wx.ID_OK:
2407 return True
2408
2409 _log.warning('syncing log file for backup to [%s]', new_name)
2410 gmLog2.flush()
2411 shutil.copy2(gmLog2._logfile_name, new_name)
2412 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2413
2416
2417
2418
2420 """This is the wx.EVT_CLOSE handler.
2421
2422 - framework still functional
2423 """
2424 _log.debug('gmTopLevelFrame.OnClose() start')
2425 self._clean_exit()
2426 self.Destroy()
2427 _log.debug('gmTopLevelFrame.OnClose() end')
2428 return True
2429
2435
2440
2448
2455
2462
2469
2479
2487
2495
2503
2511
2520
2529
2537
2554
2557
2560
2562
2563 pat = gmPerson.gmCurrentPatient()
2564 if not pat.connected:
2565 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2566 return False
2567
2568 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2569
2570 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2571 gmTools.mkdir(aDefDir)
2572
2573 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2574 dlg = wx.FileDialog (
2575 parent = self,
2576 message = _("Save patient's EMR journal as..."),
2577 defaultDir = aDefDir,
2578 defaultFile = fname,
2579 wildcard = aWildcard,
2580 style = wx.SAVE
2581 )
2582 choice = dlg.ShowModal()
2583 fname = dlg.GetPath()
2584 dlg.Destroy()
2585 if choice != wx.ID_OK:
2586 return True
2587
2588 _log.debug('exporting EMR journal to [%s]' % fname)
2589
2590 exporter = gmPatientExporter.cEMRJournalExporter()
2591
2592 wx.BeginBusyCursor()
2593 try:
2594 fname = exporter.export_to_file(filename = fname)
2595 except:
2596 wx.EndBusyCursor()
2597 gmGuiHelpers.gm_show_error (
2598 _('Error exporting patient EMR as chronological journal.'),
2599 _('EMR journal export')
2600 )
2601 raise
2602 wx.EndBusyCursor()
2603
2604 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2605
2606 return True
2607
2614
2616 curr_pat = gmPerson.gmCurrentPatient()
2617 if not curr_pat.connected:
2618 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2619 return
2620
2621 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2622 if tag is None:
2623 return
2624
2625 tag = curr_pat.add_tag(tag['pk_tag_image'])
2626 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2627 comment = wx.GetTextFromUser (
2628 message = msg,
2629 caption = _('Editing tag comment'),
2630 default_value = gmTools.coalesce(tag['comment'], u''),
2631 parent = self
2632 )
2633
2634 if comment == u'':
2635 return
2636
2637 if comment.strip() == tag['comment']:
2638 return
2639
2640 if comment == u' ':
2641 tag['comment'] = None
2642 else:
2643 tag['comment'] = comment.strip()
2644
2645 tag.save()
2646
2656
2658 curr_pat = gmPerson.gmCurrentPatient()
2659 if not curr_pat.connected:
2660 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2661 return False
2662
2663 enc = 'cp850'
2664 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2665 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2666 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2667
2670
2678
2686
2689
2696
2700
2703
2706
2709
2712
2717
2719 """Cleanup helper.
2720
2721 - should ALWAYS be called when this program is
2722 to be terminated
2723 - ANY code that should be executed before a
2724 regular shutdown should go in here
2725 - framework still functional
2726 """
2727 _log.debug('gmTopLevelFrame._clean_exit() start')
2728
2729
2730 listener = gmBackendListener.gmBackendListener()
2731 try:
2732 listener.shutdown()
2733 except:
2734 _log.exception('cannot stop backend notifications listener thread')
2735
2736
2737 if _scripting_listener is not None:
2738 try:
2739 _scripting_listener.shutdown()
2740 except:
2741 _log.exception('cannot stop scripting listener thread')
2742
2743
2744 self.clock_update_timer.Stop()
2745 gmTimer.shutdown()
2746 gmPhraseWheel.shutdown()
2747
2748
2749 for call_back in self.__pre_exit_callbacks:
2750 try:
2751 call_back()
2752 except:
2753 print "*** pre-exit callback failed ***"
2754 print call_back
2755 _log.exception('callback [%s] failed', call_back)
2756
2757
2758 gmDispatcher.send(u'application_closing')
2759
2760
2761 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2762
2763
2764 curr_width, curr_height = self.GetClientSizeTuple()
2765 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2766 dbcfg = gmCfg.cCfgSQL()
2767 dbcfg.set (
2768 option = 'main.window.width',
2769 value = curr_width,
2770 workplace = gmSurgery.gmCurrentPractice().active_workplace
2771 )
2772 dbcfg.set (
2773 option = 'main.window.height',
2774 value = curr_height,
2775 workplace = gmSurgery.gmCurrentPractice().active_workplace
2776 )
2777
2778 if _cfg.get(option = 'debug'):
2779 print '---=== GNUmed shutdown ===---'
2780 try:
2781 print _('You have to manually close this window to finalize shutting down GNUmed.')
2782 print _('This is so that you can inspect the console output at your leisure.')
2783 except UnicodeEncodeError:
2784 print 'You have to manually close this window to finalize shutting down GNUmed.'
2785 print 'This is so that you can inspect the console output at your leisure.'
2786 print '---=== GNUmed shutdown ===---'
2787
2788
2789 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2790
2791
2792 import threading
2793 _log.debug("%s active threads", threading.activeCount())
2794 for t in threading.enumerate():
2795 _log.debug('thread %s', t)
2796
2797 _log.debug('gmTopLevelFrame._clean_exit() end')
2798
2799
2800
2802
2803 if _cfg.get(option = 'slave'):
2804 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2805 _cfg.get(option = 'slave personality'),
2806 _cfg.get(option = 'xml-rpc port')
2807 )
2808 else:
2809 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2810
2812 """Update title of main window based on template.
2813
2814 This gives nice tooltips on iconified GNUmed instances.
2815
2816 User research indicates that in the title bar people want
2817 the date of birth, not the age, so please stick to this
2818 convention.
2819 """
2820 args = {}
2821
2822 pat = gmPerson.gmCurrentPatient()
2823 if pat.connected:
2824 args['pat'] = u'%s %s %s (%s) #%d' % (
2825 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2826 pat['firstnames'],
2827 pat['lastnames'],
2828 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2829 pat['pk_identity']
2830 )
2831 else:
2832 args['pat'] = _('no patient')
2833
2834 args['prov'] = u'%s%s.%s' % (
2835 gmTools.coalesce(_provider['title'], u'', u'%s '),
2836 _provider['firstnames'][:1],
2837 _provider['lastnames']
2838 )
2839
2840 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2841
2842 self.SetTitle(self.__title_template % args)
2843
2844
2846 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2847 sb.SetStatusWidths([-1, 225])
2848
2849 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2850 self._cb_update_clock()
2851
2852 self.clock_update_timer.Start(milliseconds = 1000)
2853
2855 """Displays date and local time in the second slot of the status bar"""
2856 t = time.localtime(time.time())
2857 st = time.strftime('%c', t).decode(gmI18N.get_encoding(), 'replace')
2858 self.SetStatusText(st, 1)
2859
2861 """Lock GNUmed client against unauthorized access"""
2862
2863
2864
2865 return
2866
2868 """Unlock the main notebook widgets
2869 As long as we are not logged into the database backend,
2870 all pages but the 'login' page of the main notebook widget
2871 are locked; i.e. not accessible by the user
2872 """
2873
2874
2875
2876
2877
2878 return
2879
2881 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2882
2884
2886
2887 self.__starting_up = True
2888
2889 gmExceptionHandlingWidgets.install_wx_exception_handler()
2890 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2891
2892
2893
2894
2895 self.SetAppName(u'gnumed')
2896 self.SetVendorName(u'The GNUmed Development Community.')
2897 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2898 paths.init_paths(wx = wx, app_name = u'gnumed')
2899
2900 if not self.__setup_prefs_file():
2901 return False
2902
2903 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2904
2905 self.__guibroker = gmGuiBroker.GuiBroker()
2906 self.__setup_platform()
2907
2908 if not self.__establish_backend_connection():
2909 return False
2910
2911 if not _cfg.get(option = 'skip-update-check'):
2912 self.__check_for_updates()
2913
2914 if _cfg.get(option = 'slave'):
2915 if not self.__setup_scripting_listener():
2916 return False
2917
2918
2919 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2920 frame.CentreOnScreen(wx.BOTH)
2921 self.SetTopWindow(frame)
2922 frame.Show(True)
2923
2924 if _cfg.get(option = 'debug'):
2925 self.RedirectStdio()
2926 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2927
2928
2929 print '---=== GNUmed startup ===---'
2930 print _('redirecting STDOUT/STDERR to this log window')
2931 print '---=== GNUmed startup ===---'
2932
2933 self.__setup_user_activity_timer()
2934 self.__register_events()
2935
2936 wx.CallAfter(self._do_after_init)
2937
2938 return True
2939
2941 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2942
2943 - after destroying all application windows and controls
2944 - before wx.Windows internal cleanup
2945 """
2946 _log.debug('gmApp.OnExit() start')
2947
2948 self.__shutdown_user_activity_timer()
2949
2950 if _cfg.get(option = 'debug'):
2951 self.RestoreStdio()
2952 sys.stdin = sys.__stdin__
2953 sys.stdout = sys.__stdout__
2954 sys.stderr = sys.__stderr__
2955
2956 _log.debug('gmApp.OnExit() end')
2957
2959 wx.Bell()
2960 wx.Bell()
2961 wx.Bell()
2962 _log.warning('unhandled event detected: QUERY_END_SESSION')
2963 _log.info('we should be saving ourselves from here')
2964 gmLog2.flush()
2965 print "unhandled event detected: QUERY_END_SESSION"
2966
2968 wx.Bell()
2969 wx.Bell()
2970 wx.Bell()
2971 _log.warning('unhandled event detected: END_SESSION')
2972 gmLog2.flush()
2973 print "unhandled event detected: END_SESSION"
2974
2985
2987 self.user_activity_detected = True
2988 evt.Skip()
2989
2991
2992 if self.user_activity_detected:
2993 self.elapsed_inactivity_slices = 0
2994 self.user_activity_detected = False
2995 self.elapsed_inactivity_slices += 1
2996 else:
2997 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2998
2999 pass
3000
3001 self.user_activity_timer.Start(oneShot = True)
3002
3003
3004
3006 try:
3007 kwargs['originated_in_database']
3008 print '==> got notification from database "%s":' % kwargs['signal']
3009 except KeyError:
3010 print '==> received signal from client: "%s"' % kwargs['signal']
3011
3012 del kwargs['signal']
3013 for key in kwargs.keys():
3014 print ' [%s]: %s' % (key, kwargs[key])
3015
3021
3023 self.user_activity_detected = True
3024 self.elapsed_inactivity_slices = 0
3025
3026 self.max_user_inactivity_slices = 15
3027 self.user_activity_timer = gmTimer.cTimer (
3028 callback = self._on_user_activity_timer_expired,
3029 delay = 2000
3030 )
3031 self.user_activity_timer.Start(oneShot=True)
3032
3034 try:
3035 self.user_activity_timer.Stop()
3036 del self.user_activity_timer
3037 except:
3038 pass
3039
3041 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
3042 wx.EVT_END_SESSION(self, self._on_end_session)
3043
3044
3045
3046
3047
3048 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
3049
3050 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
3051 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
3052
3053 if _cfg.get(option = 'debug'):
3054 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
3055 _log.debug('connected signal monitor')
3056
3072
3074 """Handle all the database related tasks necessary for startup."""
3075
3076
3077 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
3078
3079 from Gnumed.wxpython import gmAuthWidgets
3080 connected = gmAuthWidgets.connect_to_database (
3081 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
3082 require_version = not override
3083 )
3084 if not connected:
3085 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
3086 return False
3087
3088
3089 try:
3090 global _provider
3091 _provider = gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
3092 except ValueError:
3093 account = gmPG2.get_current_user()
3094 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
3095 msg = _(
3096 'The database account [%s] cannot be used as a\n'
3097 'staff member login for GNUmed. There was an\n'
3098 'error retrieving staff details for it.\n\n'
3099 'Please ask your administrator for help.\n'
3100 ) % account
3101 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
3102 return False
3103
3104
3105 tmp = '%s%s %s (%s = %s)' % (
3106 gmTools.coalesce(_provider['title'], ''),
3107 _provider['firstnames'],
3108 _provider['lastnames'],
3109 _provider['short_alias'],
3110 _provider['db_user']
3111 )
3112 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
3113
3114
3115 surgery = gmSurgery.gmCurrentPractice()
3116 msg = surgery.db_logon_banner
3117 if msg.strip() != u'':
3118
3119 login = gmPG2.get_default_login()
3120 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
3121 login.database,
3122 gmTools.coalesce(login.host, u'localhost')
3123 ))
3124 msg = auth + msg + u'\n\n'
3125
3126 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3127 None,
3128
3129 -1,
3130 caption = _('Verifying database'),
3131 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3132 button_defs = [
3133 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3134 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3135 ]
3136 )
3137 go_on = dlg.ShowModal()
3138 dlg.Destroy()
3139 if go_on != wx.ID_YES:
3140 _log.info('user decided to not connect to this database')
3141 return False
3142
3143
3144 self.__check_db_lang()
3145
3146 return True
3147
3149 """Setup access to a config file for storing preferences."""
3150
3151 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3152
3153 candidates = []
3154 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3155 if explicit_file is not None:
3156 candidates.append(explicit_file)
3157
3158 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3159 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3160 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3161
3162 prefs_file = None
3163 for candidate in candidates:
3164 try:
3165 open(candidate, 'a+').close()
3166 prefs_file = candidate
3167 break
3168 except IOError:
3169 continue
3170
3171 if prefs_file is None:
3172 msg = _(
3173 'Cannot find configuration file in any of:\n'
3174 '\n'
3175 ' %s\n'
3176 'You may need to use the comand line option\n'
3177 '\n'
3178 ' --conf-file=<FILE>'
3179 ) % '\n '.join(candidates)
3180 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3181 return False
3182
3183 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3184 _log.info('user preferences file: %s', prefs_file)
3185
3186 return True
3187
3189
3190 from socket import error as SocketError
3191 from Gnumed.pycommon import gmScriptingListener
3192 from Gnumed.wxpython import gmMacro
3193
3194 slave_personality = gmTools.coalesce (
3195 _cfg.get (
3196 group = u'workplace',
3197 option = u'slave personality',
3198 source_order = [
3199 ('explicit', 'return'),
3200 ('workbase', 'return'),
3201 ('user', 'return'),
3202 ('system', 'return')
3203 ]
3204 ),
3205 u'gnumed-client'
3206 )
3207 _cfg.set_option(option = 'slave personality', value = slave_personality)
3208
3209
3210 port = int (
3211 gmTools.coalesce (
3212 _cfg.get (
3213 group = u'workplace',
3214 option = u'xml-rpc port',
3215 source_order = [
3216 ('explicit', 'return'),
3217 ('workbase', 'return'),
3218 ('user', 'return'),
3219 ('system', 'return')
3220 ]
3221 ),
3222 9999
3223 )
3224 )
3225 _cfg.set_option(option = 'xml-rpc port', value = port)
3226
3227 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3228 global _scripting_listener
3229 try:
3230 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3231 except SocketError, e:
3232 _log.exception('cannot start GNUmed XML-RPC server')
3233 gmGuiHelpers.gm_show_error (
3234 aMessage = (
3235 'Cannot start the GNUmed server:\n'
3236 '\n'
3237 ' [%s]'
3238 ) % e,
3239 aTitle = _('GNUmed startup')
3240 )
3241 return False
3242
3243 return True
3244
3265
3267 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3268 _log.warning("system locale is undefined (probably meaning 'C')")
3269 return True
3270
3271
3272 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3273 db_lang = rows[0]['lang']
3274
3275 if db_lang is None:
3276 _log.debug("database locale currently not set")
3277 msg = _(
3278 "There is no language selected in the database for user [%s].\n"
3279 "Your system language is currently set to [%s].\n\n"
3280 "Do you want to set the database language to '%s' ?\n\n"
3281 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3282 checkbox_msg = _('Remember to ignore missing language')
3283 else:
3284 _log.debug("current database locale: [%s]" % db_lang)
3285 msg = _(
3286 "The currently selected database language ('%s') does\n"
3287 "not match the current system language ('%s').\n"
3288 "\n"
3289 "Do you want to set the database language to '%s' ?\n"
3290 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3291 checkbox_msg = _('Remember to ignore language mismatch')
3292
3293
3294 if db_lang == gmI18N.system_locale_level['full']:
3295 _log.debug('Database locale (%s) up to date.' % db_lang)
3296 return True
3297 if db_lang == gmI18N.system_locale_level['country']:
3298 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3299 return True
3300 if db_lang == gmI18N.system_locale_level['language']:
3301 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3302 return True
3303
3304 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3305
3306
3307 ignored_sys_lang = _cfg.get (
3308 group = u'backend',
3309 option = u'ignored mismatching system locale',
3310 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3311 )
3312
3313
3314 if gmI18N.system_locale == ignored_sys_lang:
3315 _log.info('configured to ignore system-to-database locale mismatch')
3316 return True
3317
3318
3319 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3320 None,
3321 -1,
3322 caption = _('Checking database language settings'),
3323 question = msg,
3324 button_defs = [
3325 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3326 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3327 ],
3328 show_checkbox = True,
3329 checkbox_msg = checkbox_msg,
3330 checkbox_tooltip = _(
3331 'Checking this will make GNUmed remember your decision\n'
3332 'until the system language is changed.\n'
3333 '\n'
3334 'You can also reactivate this inquiry by removing the\n'
3335 'corresponding "ignore" option from the configuration file\n'
3336 '\n'
3337 ' [%s]'
3338 ) % _cfg.get(option = 'user_preferences_file')
3339 )
3340 decision = dlg.ShowModal()
3341 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3342 dlg.Destroy()
3343
3344 if decision == wx.ID_NO:
3345 if not remember_ignoring_problem:
3346 return True
3347 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3348 gmCfg2.set_option_in_INI_file (
3349 filename = _cfg.get(option = 'user_preferences_file'),
3350 group = 'backend',
3351 option = 'ignored mismatching system locale',
3352 value = gmI18N.system_locale
3353 )
3354 return True
3355
3356
3357 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3358 if len(lang) > 0:
3359
3360
3361 rows, idx = gmPG2.run_rw_queries (
3362 link_obj = None,
3363 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3364 return_data = True
3365 )
3366 if rows[0][0]:
3367 _log.debug("Successfully set database language to [%s]." % lang)
3368 else:
3369 _log.error('Cannot set database language to [%s].' % lang)
3370 continue
3371 return True
3372
3373
3374 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3375 gmPG2.run_rw_queries(queries = [{
3376 'cmd': u'select i18n.force_curr_lang(%s)',
3377 'args': [gmI18N.system_locale_level['country']]
3378 }])
3379
3380 return True
3381
3383 try:
3384 kwargs['originated_in_database']
3385 print '==> got notification from database "%s":' % kwargs['signal']
3386 except KeyError:
3387 print '==> received signal from client: "%s"' % kwargs['signal']
3388
3389 del kwargs['signal']
3390 for key in kwargs.keys():
3391
3392 try: print ' [%s]: %s' % (key, kwargs[key])
3393 except: print 'cannot print signal information'
3394
3398
3409
3411
3412 if _cfg.get(option = 'debug'):
3413 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3414 _log.debug('gmDispatcher signal monitor activated')
3415
3416 setup_safe_wxEndBusyCursor()
3417
3418 wx.InitAllImageHandlers()
3419
3420
3421
3422 app = gmApp(redirect = False, clearSigInt = False)
3423 app.MainLoop()
3424
3425
3426
3427 if __name__ == '__main__':
3428
3429 from GNUmed.pycommon import gmI18N
3430 gmI18N.activate_locale()
3431 gmI18N.install_domain()
3432
3433 _log.info('Starting up as main module.')
3434 main()
3435
3436
3437