1
2 """GNUmed GUI client.
3
4 This contains the GUI application framework and main window
5 of the all signing all dancing GNUmed Python Reference
6 client. It relies on the <gnumed.py> launcher having set up
7 the non-GUI-related runtime environment.
8
9 copyright: authors
10 """
11
12 __author__ = "H. Herb <hherb@gnumed.net>,\
13 K. Hilbert <Karsten.Hilbert@gmx.net>,\
14 I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
15 __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
16
17
18 import sys
19 import time
20 import os
21 import os.path
22 import datetime as pyDT
23 import shutil
24 import logging
25 import urllib2
26 import subprocess
27 import glob
28
29
30
31
32 if not hasattr(sys, 'frozen'):
33 import wxversion
34 wxversion.ensureMinimal('2.8-unicode', optionsRequired=True)
35
36 try:
37 import wx
38 except ImportError:
39 print "GNUmed startup: Cannot import wxPython library."
40 print "GNUmed startup: Make sure wxPython is installed."
41 print 'CRITICAL ERROR: Error importing wxPython. Halted.'
42 raise
43
44
45
46 version = int(u'%s%s' % (wx.MAJOR_VERSION, wx.MINOR_VERSION))
47 if (version < 28) or ('unicode' not in wx.PlatformInfo):
48 print "GNUmed startup: Unsupported wxPython version (%s: %s)." % (wx.VERSION_STRING, wx.PlatformInfo)
49 print "GNUmed startup: wxPython 2.8+ with unicode support is required."
50 print 'CRITICAL ERROR: Proper wxPython version not found. Halted.'
51 raise ValueError('wxPython 2.8+ with unicode support not found')
52
53
54
55 from Gnumed.pycommon import gmCfg
56 from Gnumed.pycommon import gmPG2
57 from Gnumed.pycommon import gmDispatcher
58 from Gnumed.pycommon import gmGuiBroker
59 from Gnumed.pycommon import gmI18N
60 from Gnumed.pycommon import gmExceptions
61 from Gnumed.pycommon import gmShellAPI
62 from Gnumed.pycommon import gmTools
63 from Gnumed.pycommon import gmDateTime
64 from Gnumed.pycommon import gmHooks
65 from Gnumed.pycommon import gmBackendListener
66 from Gnumed.pycommon import gmCfg2
67 from Gnumed.pycommon import gmLog2
68 from Gnumed.pycommon import gmNetworkTools
69
70 from Gnumed.business import gmPerson
71 from Gnumed.business import gmClinicalRecord
72 from Gnumed.business import gmSurgery
73 from Gnumed.business import gmEMRStructItems
74 from Gnumed.business import gmVaccination
75 from Gnumed.business import gmArriba
76 from Gnumed.business import gmStaff
77
78 from Gnumed.exporters import gmPatientExporter
79
80 from Gnumed.wxpython import gmGuiHelpers
81 from Gnumed.wxpython import gmHorstSpace
82 from Gnumed.wxpython import gmEMRBrowser
83 from Gnumed.wxpython import gmDemographicsWidgets
84 from Gnumed.wxpython import gmEMRStructWidgets
85 from Gnumed.wxpython import gmPatSearchWidgets
86 from Gnumed.wxpython import gmAllergyWidgets
87 from Gnumed.wxpython import gmListWidgets
88 from Gnumed.wxpython import gmProviderInboxWidgets
89 from Gnumed.wxpython import gmCfgWidgets
90 from Gnumed.wxpython import gmExceptionHandlingWidgets
91 from Gnumed.wxpython import gmNarrativeWidgets
92 from Gnumed.wxpython import gmPhraseWheel
93 from Gnumed.wxpython import gmMedicationWidgets
94 from Gnumed.wxpython import gmStaffWidgets
95 from Gnumed.wxpython import gmDocumentWidgets
96 from Gnumed.wxpython import gmTimer
97 from Gnumed.wxpython import gmMeasurementWidgets
98 from Gnumed.wxpython import gmFormWidgets
99 from Gnumed.wxpython import gmSnellen
100 from Gnumed.wxpython import gmVaccWidgets
101 from Gnumed.wxpython import gmPersonContactWidgets
102 from Gnumed.wxpython import gmI18nWidgets
103 from Gnumed.wxpython import gmCodingWidgets
104 from Gnumed.wxpython import gmOrganizationWidgets
105 from Gnumed.wxpython import gmAuthWidgets
106 from Gnumed.wxpython import gmFamilyHistoryWidgets
107 from Gnumed.wxpython import gmDataPackWidgets
108 from Gnumed.wxpython import gmContactWidgets
109 from Gnumed.wxpython import gmAddressWidgets
110 from Gnumed.wxpython import gmBillingWidgets
111 from Gnumed.wxpython import gmKeywordExpansionWidgets
112
113
114 try:
115 _('dummy-no-need-to-translate-but-make-epydoc-happy')
116 except NameError:
117 _ = lambda x:x
118
119 _cfg = gmCfg2.gmCfgData()
120 _provider = None
121 _scripting_listener = None
122 _original_wxEndBusyCursor = None
123
124 _log = logging.getLogger('gm.main')
125 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
126
127
129 """GNUmed client's main windows frame.
130
131 This is where it all happens. Avoid popping up any other windows.
132 Most user interaction should happen to and from widgets within this frame
133 """
134
135 - def __init__(self, parent, id, title, size=wx.DefaultSize):
136 """You'll have to browse the source to understand what the constructor does
137 """
138 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
139
140 self.__setup_font()
141
142 self.__gb = gmGuiBroker.GuiBroker()
143 self.__pre_exit_callbacks = []
144 self.bar_width = -1
145 self.menu_id2plugin = {}
146
147 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
148
149 self.__setup_main_menu()
150 self.setup_statusbar()
151 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
152 gmTools.coalesce(_provider['title'], ''),
153 _provider['firstnames'][:1],
154 _provider['lastnames'],
155 _provider['short_alias'],
156 _provider['db_user']
157 ))
158
159 self.__set_window_title_template()
160 self.__update_window_title()
161
162
163
164
165
166 self.SetIcon(gmTools.get_icon(wx = wx))
167
168 self.__register_events()
169
170 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
171 self.vbox = wx.BoxSizer(wx.VERTICAL)
172 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
173
174 self.SetAutoLayout(True)
175 self.SetSizerAndFit(self.vbox)
176
177
178
179
180
181 self.__set_GUI_size()
182
183
185
186 font = self.GetFont()
187 _log.debug('system default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
188
189 desired_font_face = _cfg.get (
190 group = u'workplace',
191 option = u'client font',
192 source_order = [
193 ('explicit', 'return'),
194 ('workbase', 'return'),
195 ('local', 'return'),
196 ('user', 'return'),
197 ('system', 'return')
198 ]
199 )
200
201 fonts2try = []
202 if desired_font_face is not None:
203 _log.info('client is configured to use font [%s]', desired_font_face)
204 fonts2try.append(desired_font_face)
205
206 if wx.Platform == '__WXMSW__':
207 sane_font_face = u'DejaVu Sans'
208 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face)
209 fonts2try.append(sane_font_face)
210
211 if len(fonts2try) == 0:
212 return
213
214 for font_face in fonts2try:
215 success = font.SetFaceName(font_face)
216 if success:
217 self.SetFont(font)
218 _log.debug('switched font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
219 return
220 font = self.GetFont()
221 _log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face)
222
223 return
224
226 """Try to get previous window size from backend."""
227
228 cfg = gmCfg.cCfgSQL()
229
230
231 width = int(cfg.get2 (
232 option = 'main.window.width',
233 workplace = gmSurgery.gmCurrentPractice().active_workplace,
234 bias = 'workplace',
235 default = 800
236 ))
237
238
239 height = int(cfg.get2 (
240 option = 'main.window.height',
241 workplace = gmSurgery.gmCurrentPractice().active_workplace,
242 bias = 'workplace',
243 default = 600
244 ))
245
246 dw = wx.DisplaySize()[0]
247 dh = wx.DisplaySize()[1]
248
249 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
250 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
251 _log.debug('previous GUI size [%s:%s]', width, height)
252
253
254 if width > dw:
255 _log.debug('adjusting GUI width from %s to %s', width, dw)
256 width = dw
257
258 if height > dh:
259 _log.debug('adjusting GUI height from %s to %s', height, dh)
260 height = dh
261
262
263 if width < 100:
264 _log.debug('adjusting GUI width to minimum of 100 pixel')
265 width = 100
266 if height < 100:
267 _log.debug('adjusting GUI height to minimum of 100 pixel')
268 height = 100
269
270 _log.info('setting GUI to size [%s:%s]', width, height)
271
272 self.SetClientSize(wx.Size(width, height))
273
275 """Create the main menu entries.
276
277 Individual entries are farmed out to the modules.
278
279 menu item template:
280
281 item = menu_emr_edit.Append(-1, _(''), _(''))
282 self.Bind(wx.EVT_MENU, self__on_, item)
283 """
284 global wx
285 self.mainmenu = wx.MenuBar()
286 self.__gb['main.mainmenu'] = self.mainmenu
287
288
289 menu_gnumed = wx.Menu()
290
291 self.menu_plugins = wx.Menu()
292 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
293
294 ID = wx.NewId()
295 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
296 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
297
298 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
299 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
300
301
302 menu_gnumed.AppendSeparator()
303
304
305 menu_config = wx.Menu()
306
307 item = menu_config.Append(-1, _('All options'), _('List all options as configured in the database.'))
308 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
309
310
311 menu_cfg_db = wx.Menu()
312
313 ID = wx.NewId()
314 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
315 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
316
317 ID = wx.NewId()
318 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
319 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
320
321 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
322
323
324 menu_cfg_client = wx.Menu()
325
326 ID = wx.NewId()
327 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
328 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
329
330 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
331 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
332
333 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
334
335
336 menu_cfg_ui = wx.Menu()
337
338
339 menu_cfg_doc = wx.Menu()
340
341 ID = wx.NewId()
342 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
343 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
344
345 ID = wx.NewId()
346 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
347 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
348
349 ID = wx.NewId()
350 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
351 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
352
353 item = menu_cfg_doc.Append(-1, _('Generate UUID'), _('Whether to generate UUIDs for new documents.'))
354 self.Bind(wx.EVT_MENU, self.__on_configure_generate_doc_uuid, item)
355
356 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
357
358
359 menu_cfg_update = wx.Menu()
360
361 ID = wx.NewId()
362 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
363 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
364
365 ID = wx.NewId()
366 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
367 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
368
369 ID = wx.NewId()
370 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
371 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
372
373 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
374
375
376 menu_cfg_pat_search = wx.Menu()
377
378 ID = wx.NewId()
379 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
380 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
381
382 ID = wx.NewId()
383 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
384 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
385
386 ID = wx.NewId()
387 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
388 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
389
390 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
391 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
392
393 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
394 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
395
396 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
397
398
399 menu_cfg_soap_editing = wx.Menu()
400
401 ID = wx.NewId()
402 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
403 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
404
405 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.'))
406 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item)
407
408 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
409
410 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
411
412
413 menu_cfg_ext_tools = wx.Menu()
414
415
416
417
418
419 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
420 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
421
422 ID = wx.NewId()
423 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
424 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
425
426 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
427 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
428
429 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
430 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
431
432
433
434
435 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
436 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
437
438 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
439 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
440
441 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
442 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
443
444 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
445 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
446
447 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
448
449
450 menu_cfg_bill = wx.Menu()
451
452 item = menu_cfg_bill.Append(-1, _('Invoice template (no VAT)'), _('Select the template for printing an invoice without VAT.'))
453 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_template_no_vat, item)
454
455 item = menu_cfg_bill.Append(-1, _('Invoice template (with VAT)'), _('Select the template for printing an invoice with VAT.'))
456 self.Bind(wx.EVT_MENU, self.__on_cfg_invoice_template_with_vat, item)
457
458 item = menu_cfg_bill.Append(-1, _('Catalogs URL'), _('URL for billing catalogs (schedules of fees).'))
459 self.Bind(wx.EVT_MENU, self.__on_configure_billing_catalogs_url, item)
460
461
462 menu_cfg_emr = wx.Menu()
463
464 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
465 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
466
467 item = menu_cfg_emr.Append(-1, _('Default Gnuplot template'), _('Select the default template for plotting test results.'))
468 self.Bind(wx.EVT_MENU, self.__on_cfg_default_gnuplot_template, item)
469
470 item = menu_cfg_emr.Append(-1, _('Fallback provider'), _('Select the doctor to fall back to for patients without a primary provider.'))
471 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
472
473
474 menu_cfg_encounter = wx.Menu()
475
476 ID = wx.NewId()
477 menu_cfg_encounter.Append(ID, _('Edit before patient change'), _('Edit encounter details before change of patient.'))
478 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
479
480 ID = wx.NewId()
481 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
482 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
483
484 ID = wx.NewId()
485 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
486 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
487
488 ID = wx.NewId()
489 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
490 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
491
492 ID = wx.NewId()
493 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
494 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
495
496 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
497
498
499 menu_cfg_episode = wx.Menu()
500
501 ID = wx.NewId()
502 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
503 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
504
505 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
506
507 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
508 menu_config.AppendMenu(wx.NewId(), _('Billing ...'), menu_cfg_bill)
509 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
510
511
512 menu_master_data = wx.Menu()
513
514 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
515 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
516
517 item = menu_master_data.Append(-1, _('Install data packs'), _('Install reference data from data packs.'))
518 self.Bind(wx.EVT_MENU, self.__on_install_data_packs, item)
519
520 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
521 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
522
523 item = menu_master_data.Append(-1, _('Update LOINC'), _('Download and install LOINC reference data.'))
524 self.Bind(wx.EVT_MENU, self.__on_update_loinc, item)
525
526 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
527 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
528
529 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
530
531
532 menu_users = wx.Menu()
533
534 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
535 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
536
537 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
538 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
539
540 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
541 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
542
543 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
544
545
546 menu_gnumed.AppendSeparator()
547
548 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
549 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
550
551 self.mainmenu.Append(menu_gnumed, '&GNUmed')
552
553
554 menu_person = wx.Menu()
555
556 ID_CREATE_PATIENT = wx.NewId()
557 menu_person.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
558 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
559
560 ID_LOAD_EXT_PAT = wx.NewId()
561 menu_person.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
562 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
563
564 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
565 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
566
567 ID_DEL_PAT = wx.NewId()
568 menu_person.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
569 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
570
571 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
572 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
573
574 menu_person.AppendSeparator()
575
576 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
577 menu_person.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
578 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
579
580
581 ID = wx.NewId()
582 menu_person.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
583 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
584
585 menu_person.AppendSeparator()
586
587 self.mainmenu.Append(menu_person, '&Person')
588 self.__gb['main.patientmenu'] = menu_person
589
590
591 menu_emr = wx.Menu()
592
593
594 menu_emr_edit = wx.Menu()
595
596 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'))
597 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
598
599 item = menu_emr_edit.Append(-1, _('&Episode'), _('Add an episode of illness to the EMR of the active patient'))
600 self.Bind(wx.EVT_MENU, self.__on_add_episode, item)
601
602 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
603 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
604
605 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
606 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
607
608 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
609 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
610
611 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospitalizations.'))
612 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
613
614 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
615 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
616
617 item = menu_emr_edit.Append(-1, _('&Measurements'), _('Add (a) measurement result(s) for the current patient.'))
618 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
619
620 item = menu_emr_edit.Append(-1, _('&Vaccinations'), _('Add (a) vaccination(s) for the current patient.'))
621 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
622
623 item = menu_emr_edit.Append(-1, _('&Family history (FHx)'), _('Manage family history.'))
624 self.Bind(wx.EVT_MENU, self.__on_manage_fhx, item)
625
626 item = menu_emr_edit.Append(-1, _('&Encounters'), _('List all encounters including empty ones.'))
627 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
628
629 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
630
631
632 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
633 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
634
635 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
636 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
637
638
639
640
641
642 item = menu_emr.Append(-1, _('Statistics'), _('Show a high-level statistic summary of the EMR.'))
643 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
644
645
646
647
648 menu_emr.AppendSeparator()
649
650
651 menu_emr_export = wx.Menu()
652
653 ID_EXPORT_EMR_ASCII = wx.NewId()
654 menu_emr_export.Append (
655 ID_EXPORT_EMR_ASCII,
656 _('Text document'),
657 _("Export the EMR of the active patient into a text file")
658 )
659 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
660
661 ID_EXPORT_EMR_JOURNAL = wx.NewId()
662 menu_emr_export.Append (
663 ID_EXPORT_EMR_JOURNAL,
664 _('Journal'),
665 _("Export the EMR of the active patient as a chronological journal into a text file")
666 )
667 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
668
669 ID_EXPORT_MEDISTAR = wx.NewId()
670 menu_emr_export.Append (
671 ID_EXPORT_MEDISTAR,
672 _('MEDISTAR import format'),
673 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
674 )
675 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
676
677 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
678
679 menu_emr.AppendSeparator()
680
681 self.mainmenu.Append(menu_emr, _("&EMR"))
682 self.__gb['main.emrmenu'] = menu_emr
683
684
685 menu_paperwork = wx.Menu()
686
687 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
688 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
689
690 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
691
692
693 self.menu_tools = wx.Menu()
694
695 item = self.menu_tools.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
696 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
697
698 ID_DICOM_VIEWER = wx.NewId()
699 viewer = _('no viewer installed')
700 if gmShellAPI.detect_external_binary(binary = 'ginkgocadx')[0]:
701 viewer = u'Ginkgo CADx'
702 elif os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
703 viewer = u'OsiriX'
704 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
705 viewer = u'Aeskulap'
706 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
707 viewer = u'AMIDE'
708 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
709 viewer = u'DicomScope'
710 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
711 viewer = u'(x)medcon'
712 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)
713 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
714 if viewer == _('no viewer installed'):
715 _log.info('neither of Ginkgo CADx / OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
716 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
717
718
719
720
721
722 ID = wx.NewId()
723 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
724 wx.EVT_MENU(self, ID, self.__on_snellen)
725
726 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
727 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
728
729 ID_DICOM_VIEWER = wx.NewId()
730 self.menu_tools.Append(ID_DICOM_VIEWER, u'arriba', _('arriba: cardiovascular risk assessment (%s).') % u'www.arriba-hausarzt.de')
731 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_arriba)
732 if not gmShellAPI.detect_external_binary(binary = 'arriba')[0]:
733 _log.info('<arriba> not found, disabling "arriba" menu item')
734 self.menu_tools.Enable(id = ID_DICOM_VIEWER, enable = False)
735
736
737
738 self.menu_tools.AppendSeparator()
739
740 self.mainmenu.Append(self.menu_tools, _("&Tools"))
741 self.__gb['main.toolsmenu'] = self.menu_tools
742
743
744 menu_knowledge = wx.Menu()
745
746
747 menu_drug_dbs = wx.Menu()
748
749 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
750 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
751
752
753
754
755
756
757 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
758
759 menu_id = wx.NewId()
760 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
761 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
762
763
764
765
766 ID_MEDICAL_LINKS = wx.NewId()
767 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
768 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
769
770 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
771 self.__gb['main.knowledgemenu'] = menu_knowledge
772
773
774 self.menu_office = wx.Menu()
775
776 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
777 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
778
779 self.menu_office.AppendSeparator()
780
781 item = self.menu_office.Append(-1, _('List bills'), _('List all bills across all patients.'))
782 self.Bind(wx.EVT_MENU, self.__on_show_all_bills, item)
783
784 self.mainmenu.Append(self.menu_office, _('&Office'))
785 self.__gb['main.officemenu'] = self.menu_office
786
787
788 help_menu = wx.Menu()
789
790 ID = wx.NewId()
791 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
792 wx.EVT_MENU(self, ID, self.__on_display_wiki)
793
794 ID = wx.NewId()
795 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
796 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
797
798 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
799 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
800
801 item = help_menu.Append(-1, _('&Clear status line'), _('Clear out the status line.'))
802 self.Bind(wx.EVT_MENU, self.__on_clear_status_line, item)
803
804 menu_debugging = wx.Menu()
805
806 ID_SCREENSHOT = wx.NewId()
807 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
808 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
809
810 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
811 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
812
813 ID = wx.NewId()
814 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
815 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
816
817 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
818 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
819
820 ID = wx.NewId()
821 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
822 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
823
824 ID_UNBLOCK = wx.NewId()
825 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
826 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
827
828 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
829 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
830
831
832
833
834 if _cfg.get(option = 'debug'):
835 ID_TOGGLE_PAT_LOCK = wx.NewId()
836 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
837 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
838
839 ID_TEST_EXCEPTION = wx.NewId()
840 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
841 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
842
843 ID = wx.NewId()
844 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
845 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
846 try:
847 import wx.lib.inspection
848 except ImportError:
849 menu_debugging.Enable(id = ID, enable = False)
850
851 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
852
853 help_menu.AppendSeparator()
854
855 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
856 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
857
858 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
859 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
860
861 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors'))
862 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item)
863
864 help_menu.AppendSeparator()
865
866 self.mainmenu.Append(help_menu, _("&Help"))
867
868 self.__gb['main.helpmenu'] = help_menu
869
870
871 self.SetMenuBar(self.mainmenu)
872
875
876
877
879 """register events we want to react to"""
880
881 wx.EVT_CLOSE(self, self.OnClose)
882 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
883 wx.EVT_END_SESSION(self, self._on_end_session)
884
885 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
886 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
887 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
888 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
889 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
890 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
891 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
892 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
893
894 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
895
896 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
897
898 _log.debug('registering plugin with menu system')
899 _log.debug(' generic name: %s', plugin_name)
900 _log.debug(' class name: %s', class_name)
901 _log.debug(' specific menu: %s', menu_name)
902 _log.debug(' menu item: %s', menu_item_name)
903
904
905 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
906 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
907 self.menu_id2plugin[item.Id] = class_name
908
909
910 if menu_name is not None:
911 menu = self.__gb['main.%smenu' % menu_name]
912 item = menu.Append(-1, menu_item_name, menu_help_string)
913 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
914 self.menu_id2plugin[item.Id] = class_name
915
916 return True
917
919 gmDispatcher.send (
920 signal = u'display_widget',
921 name = self.menu_id2plugin[evt.Id]
922 )
923
925 wx.Bell()
926 wx.Bell()
927 wx.Bell()
928 _log.warning('unhandled event detected: QUERY_END_SESSION')
929 _log.info('we should be saving ourselves from here')
930 gmLog2.flush()
931 print "unhandled event detected: QUERY_END_SESSION"
932
934 wx.Bell()
935 wx.Bell()
936 wx.Bell()
937 _log.warning('unhandled event detected: END_SESSION')
938 gmLog2.flush()
939 print "unhandled event detected: END_SESSION"
940
942 if not callable(callback):
943 raise TypeError(u'callback [%s] not callable' % callback)
944
945 self.__pre_exit_callbacks.append(callback)
946
947 - def _on_set_statustext_pubsub(self, context=None):
948 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
949 wx.CallAfter(self.SetStatusText, msg)
950
951 try:
952 if context.data['beep']:
953 wx.Bell()
954 except KeyError:
955 pass
956
957 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
958
959 if msg is None:
960 msg = _('programmer forgot to specify status message')
961
962 if loglevel is not None:
963 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
964
965 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
966 wx.CallAfter(self.SetStatusText, msg)
967
968 if beep:
969 wx.Bell()
970
972 wx.CallAfter(self.__on_db_maintenance_warning)
973
975
976 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
977 wx.Bell()
978 if not wx.GetApp().IsActive():
979 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
980
981 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
982
983 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
984 None,
985 -1,
986 caption = _('Database shutdown warning'),
987 question = _(
988 'The database will be shut down for maintenance\n'
989 'in a few minutes.\n'
990 '\n'
991 'In order to not suffer any loss of data you\n'
992 'will need to save your current work and log\n'
993 'out of this GNUmed client.\n'
994 ),
995 button_defs = [
996 {
997 u'label': _('Close now'),
998 u'tooltip': _('Close this GNUmed client immediately.'),
999 u'default': False
1000 },
1001 {
1002 u'label': _('Finish work'),
1003 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
1004 u'default': True
1005 }
1006 ]
1007 )
1008 decision = dlg.ShowModal()
1009 if decision == wx.ID_YES:
1010 top_win = wx.GetApp().GetTopWindow()
1011 wx.CallAfter(top_win.Close)
1012
1014 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
1015
1017
1018 if not wx.GetApp().IsActive():
1019 if urgent:
1020 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
1021 else:
1022 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
1023
1024 if msg is not None:
1025 self.SetStatusText(msg)
1026
1027 if urgent:
1028 wx.Bell()
1029
1030 gmHooks.run_hook_script(hook = u'request_user_attention')
1031
1033 wx.CallAfter(self.__on_pat_name_changed)
1034
1036 self.__update_window_title()
1037
1038 - def _on_post_patient_selection(self, **kwargs):
1039 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
1040
1042 self.__update_window_title()
1043 gmDispatcher.send(signal = 'statustext', msg = u'')
1044 try:
1045 gmHooks.run_hook_script(hook = u'post_patient_activation')
1046 except:
1047 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
1048 raise
1049
1051 return self.__sanity_check_encounter()
1052
1114
1115
1116
1119
1126
1127
1128
1144
1167
1169 from Gnumed.wxpython import gmAbout
1170 contribs = gmAbout.cContributorsDlg (
1171 parent = self,
1172 id = -1,
1173 title = _('GNUmed contributors'),
1174 size = wx.Size(400,600),
1175 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1176 )
1177 contribs.ShowModal()
1178 del contribs
1179 del gmAbout
1180
1181
1182
1184 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1185 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1186 self.Close(True)
1187 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1188
1191
1193 send = gmGuiHelpers.gm_show_question (
1194 _('This will send a notification about database downtime\n'
1195 'to all GNUmed clients connected to your database.\n'
1196 '\n'
1197 'Do you want to send the notification ?\n'
1198 ),
1199 _('Announcing database maintenance downtime')
1200 )
1201 if not send:
1202 return
1203 gmPG2.send_maintenance_notification()
1204
1205
1208
1209
1210
1223
1224 gmCfgWidgets.configure_string_option (
1225 message = _(
1226 'Some network installations cannot cope with loading\n'
1227 'documents of arbitrary size in one piece from the\n'
1228 'database (mainly observed on older Windows versions)\n.'
1229 '\n'
1230 'Under such circumstances documents need to be retrieved\n'
1231 'in chunks and reassembled on the client.\n'
1232 '\n'
1233 'Here you can set the size (in Bytes) above which\n'
1234 'GNUmed will retrieve documents in chunks. Setting this\n'
1235 'value to 0 will disable the chunking protocol.'
1236 ),
1237 option = 'horstspace.blob_export_chunk_size',
1238 bias = 'workplace',
1239 default_value = 1024 * 1024,
1240 validator = is_valid
1241 )
1242
1243
1244
1312
1316
1317
1318
1327
1328 gmCfgWidgets.configure_string_option (
1329 message = _(
1330 'When GNUmed cannot find an OpenOffice server it\n'
1331 'will try to start one. OpenOffice, however, needs\n'
1332 'some time to fully start up.\n'
1333 '\n'
1334 'Here you can set the time for GNUmed to wait for OOo.\n'
1335 ),
1336 option = 'external.ooo.startup_settle_time',
1337 bias = 'workplace',
1338 default_value = 2.0,
1339 validator = is_valid
1340 )
1341
1344
1359
1360 gmCfgWidgets.configure_string_option (
1361 message = _(
1362 'GNUmed will use this URL to access a website which lets\n'
1363 'you report an adverse drug reaction (ADR).\n'
1364 '\n'
1365 'If you leave this empty it will fall back\n'
1366 'to an URL for reporting ADRs in Germany.'
1367 ),
1368 option = 'external.urls.report_ADR',
1369 bias = 'user',
1370 default_value = german_default,
1371 validator = is_valid
1372 )
1373
1387
1388 gmCfgWidgets.configure_string_option (
1389 message = _(
1390 'GNUmed will use this URL to access a website which lets\n'
1391 'you report an adverse vaccination reaction (vADR).\n'
1392 '\n'
1393 'If you set it to a specific address that URL must be\n'
1394 'accessible now. If you leave it empty it will fall back\n'
1395 'to the URL for reporting other adverse drug reactions.'
1396 ),
1397 option = 'external.urls.report_vaccine_ADR',
1398 bias = 'user',
1399 default_value = german_default,
1400 validator = is_valid
1401 )
1402
1416
1417 gmCfgWidgets.configure_string_option (
1418 message = _(
1419 'GNUmed will use this URL to access an encyclopedia of\n'
1420 'measurement/lab methods from within the measurments grid.\n'
1421 '\n'
1422 'You can leave this empty but to set it to a specific\n'
1423 'address the URL must be accessible now.'
1424 ),
1425 option = 'external.urls.measurements_encyclopedia',
1426 bias = 'user',
1427 default_value = german_default,
1428 validator = is_valid
1429 )
1430
1444
1445 gmCfgWidgets.configure_string_option (
1446 message = _(
1447 'GNUmed will use this URL to access a page showing\n'
1448 'vaccination schedules.\n'
1449 '\n'
1450 'You can leave this empty but to set it to a specific\n'
1451 'address the URL must be accessible now.'
1452 ),
1453 option = 'external.urls.vaccination_plans',
1454 bias = 'user',
1455 default_value = german_default,
1456 validator = is_valid
1457 )
1458
1471
1472 gmCfgWidgets.configure_string_option (
1473 message = _(
1474 'Enter the shell command with which to start the\n'
1475 'the ACS risk assessment calculator.\n'
1476 '\n'
1477 'GNUmed will try to verify the path which may,\n'
1478 'however, fail if you are using an emulator such\n'
1479 'as Wine. Nevertheless, starting the calculator\n'
1480 'will work as long as the shell command is correct\n'
1481 'despite the failing test.'
1482 ),
1483 option = 'external.tools.acs_risk_calculator_cmd',
1484 bias = 'user',
1485 validator = is_valid
1486 )
1487
1490
1503
1504 gmCfgWidgets.configure_string_option (
1505 message = _(
1506 'Enter the shell command with which to start\n'
1507 'the FreeDiams drug database frontend.\n'
1508 '\n'
1509 'GNUmed will try to verify that path.'
1510 ),
1511 option = 'external.tools.freediams_cmd',
1512 bias = 'workplace',
1513 default_value = None,
1514 validator = is_valid
1515 )
1516
1529
1530 gmCfgWidgets.configure_string_option (
1531 message = _(
1532 'Enter the shell command with which to start the\n'
1533 'the IFAP drug database.\n'
1534 '\n'
1535 'GNUmed will try to verify the path which may,\n'
1536 'however, fail if you are using an emulator such\n'
1537 'as Wine. Nevertheless, starting IFAP will work\n'
1538 'as long as the shell command is correct despite\n'
1539 'the failing test.'
1540 ),
1541 option = 'external.ifap-win.shell_command',
1542 bias = 'workplace',
1543 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1544 validator = is_valid
1545 )
1546
1547
1548
1597
1598
1599
1616
1619
1622
1627
1628 gmCfgWidgets.configure_string_option (
1629 message = _(
1630 'When a patient is activated GNUmed checks the\n'
1631 "proximity of the patient's birthday.\n"
1632 '\n'
1633 'If the birthday falls within the range of\n'
1634 ' "today %s <the interval you set here>"\n'
1635 'GNUmed will remind you of the recent or\n'
1636 'imminent anniversary.'
1637 ) % u'\u2213',
1638 option = u'patient_search.dob_warn_interval',
1639 bias = 'user',
1640 default_value = '1 week',
1641 validator = is_valid
1642 )
1643
1645
1646 gmCfgWidgets.configure_boolean_option (
1647 parent = self,
1648 question = _(
1649 'When adding progress notes do you want to\n'
1650 'allow opening several unassociated, new\n'
1651 'episodes for a patient at once ?\n'
1652 '\n'
1653 'This can be particularly helpful when entering\n'
1654 'progress notes on entirely new patients presenting\n'
1655 'with a multitude of problems on their first visit.'
1656 ),
1657 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1658 button_tooltips = [
1659 _('Yes, allow for multiple new episodes concurrently.'),
1660 _('No, only allow editing one new episode at a time.')
1661 ]
1662 )
1663
1665
1666 gmCfgWidgets.configure_boolean_option (
1667 parent = self,
1668 question = _(
1669 'When activating a patient, do you want GNUmed to\n'
1670 'auto-open editors for all active problems that were\n'
1671 'touched upon during the current and the most recent\n'
1672 'encounter ?'
1673 ),
1674 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1675 button_tooltips = [
1676 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1677 _('No, only auto-open one editor for a new, unassociated problem.')
1678 ]
1679 )
1680
1726
1727
1728
1731
1734
1747
1748 gmCfgWidgets.configure_string_option (
1749 message = _(
1750 'GNUmed will use this URL to let you browse\n'
1751 'billing catalogs (schedules of fees).\n'
1752 '\n'
1753 'You can leave this empty but to set it to a specific\n'
1754 'address the URL must be accessible now.'
1755 ),
1756 option = 'external.urls.schedules_of_fees',
1757 bias = 'user',
1758 default_value = german_default,
1759 validator = is_valid
1760 )
1761
1762
1763
1766
1769
1772
1786
1788 gmCfgWidgets.configure_boolean_option (
1789 parent = self,
1790 question = _(
1791 'Do you want GNUmed to show the encounter\n'
1792 'details editor when changing the active patient ?'
1793 ),
1794 option = 'encounter.show_editor_before_patient_change',
1795 button_tooltips = [
1796 _('Yes, show the encounter editor if it seems appropriate.'),
1797 _('No, never show the encounter editor even if it would seem useful.')
1798 ]
1799 )
1800
1805
1806 gmCfgWidgets.configure_string_option (
1807 message = _(
1808 'When a patient is activated GNUmed checks the\n'
1809 'chart for encounters lacking any entries.\n'
1810 '\n'
1811 'Any such encounters older than what you set\n'
1812 'here will be removed from the medical record.\n'
1813 '\n'
1814 'To effectively disable removal of such encounters\n'
1815 'set this option to an improbable value.\n'
1816 ),
1817 option = 'encounter.ttl_if_empty',
1818 bias = 'user',
1819 default_value = '1 week',
1820 validator = is_valid
1821 )
1822
1827
1828 gmCfgWidgets.configure_string_option (
1829 message = _(
1830 'When a patient is activated GNUmed checks the\n'
1831 'age of the most recent encounter.\n'
1832 '\n'
1833 'If that encounter is younger than this age\n'
1834 'the existing encounter will be continued.\n'
1835 '\n'
1836 '(If it is really old a new encounter is\n'
1837 ' started, or else GNUmed will ask you.)\n'
1838 ),
1839 option = 'encounter.minimum_ttl',
1840 bias = 'user',
1841 default_value = '1 hour 30 minutes',
1842 validator = is_valid
1843 )
1844
1849
1850 gmCfgWidgets.configure_string_option (
1851 message = _(
1852 'When a patient is activated GNUmed checks the\n'
1853 'age of the most recent encounter.\n'
1854 '\n'
1855 'If that encounter is older than this age\n'
1856 'GNUmed will always start a new encounter.\n'
1857 '\n'
1858 '(If it is very recent the existing encounter\n'
1859 ' is continued, or else GNUmed will ask you.)\n'
1860 ),
1861 option = 'encounter.maximum_ttl',
1862 bias = 'user',
1863 default_value = '6 hours',
1864 validator = is_valid
1865 )
1866
1875
1876 gmCfgWidgets.configure_string_option (
1877 message = _(
1878 'At any time there can only be one open (ongoing)\n'
1879 'episode for each health issue.\n'
1880 '\n'
1881 'When you try to open (add data to) an episode on a health\n'
1882 'issue GNUmed will check for an existing open episode on\n'
1883 'that issue. If there is any it will check the age of that\n'
1884 'episode. The episode is closed if it has been dormant (no\n'
1885 'data added, that is) for the period of time (in days) you\n'
1886 'set here.\n'
1887 '\n'
1888 "If the existing episode hasn't been dormant long enough\n"
1889 'GNUmed will consult you what to do.\n'
1890 '\n'
1891 'Enter maximum episode dormancy in DAYS:'
1892 ),
1893 option = 'episode.ttl',
1894 bias = 'user',
1895 default_value = 60,
1896 validator = is_valid
1897 )
1898
1929
1944
1969
1981
1982 gmCfgWidgets.configure_string_option (
1983 message = _(
1984 'GNUmed can check for new releases being available. To do\n'
1985 'so it needs to load version information from an URL.\n'
1986 '\n'
1987 'The default URL is:\n'
1988 '\n'
1989 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1990 '\n'
1991 'but you can configure any other URL locally. Note\n'
1992 'that you must enter the location as a valid URL.\n'
1993 'Depending on the URL the client will need online\n'
1994 'access when checking for updates.'
1995 ),
1996 option = u'horstspace.update.url',
1997 bias = u'workplace',
1998 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1999 validator = is_valid
2000 )
2001
2019
2036
2053
2064
2065 gmCfgWidgets.configure_string_option (
2066 message = _(
2067 'GNUmed can show the document review dialog after\n'
2068 'calling the appropriate viewer for that document.\n'
2069 '\n'
2070 'Select the conditions under which you want\n'
2071 'GNUmed to do so:\n'
2072 '\n'
2073 ' 0: never display the review dialog\n'
2074 ' 1: always display the dialog\n'
2075 ' 2: only if there is no previous review by me\n'
2076 ' 3: only if there is no previous review at all\n'
2077 ' 4: only if there is no review by the responsible reviewer\n'
2078 '\n'
2079 'Note that if a viewer is configured to not block\n'
2080 'GNUmed during document display the review dialog\n'
2081 'will actually appear in parallel to the viewer.'
2082 ),
2083 option = u'horstspace.document_viewer.review_after_display',
2084 bias = u'user',
2085 default_value = 3,
2086 validator = is_valid
2087 )
2088
2090
2091
2092 master_data_lists = [
2093 'adr',
2094 'billables',
2095 'drugs',
2096 'hints',
2097 'codes',
2098 'communication_channel_types',
2099 'substances_in_brands',
2100 'substances',
2101 'labs',
2102 'form_templates',
2103 'doc_types',
2104 'enc_types',
2105 'text_expansions',
2106 'meta_test_types',
2107 'orgs',
2108 'patient_tags',
2109 'provinces',
2110 'db_translations',
2111 'ref_data_sources',
2112 'test_types',
2113 'vacc_indications',
2114 'vaccines',
2115 'workplaces'
2116 ]
2117
2118 master_data_list_names = {
2119 'adr': _('Addresses (likely slow)'),
2120 'drugs': _('Branded drugs (as marketed)'),
2121 'hints': _('Clinical hints'),
2122 'codes': _('Codes and their respective terms'),
2123 'communication_channel_types': _('Communication channel types'),
2124 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2125 'labs': _('Diagnostic organizations (path labs, ...)'),
2126 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2127 'doc_types': _('Document types'),
2128 'enc_types': _('Encounter types'),
2129 'text_expansions': _('Keyword based text expansion macros'),
2130 'meta_test_types': _('Meta test/measurement types'),
2131 'orgs': _('Organizations with their units, addresses, and comm channels'),
2132 'patient_tags': _('Patient tags'),
2133 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2134 'db_translations': _('String translations in the database'),
2135 'test_types': _('Test/measurement types'),
2136 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2137 'vaccines': _('Vaccines'),
2138 'workplaces': _('Workplace profiles (which plugins to load)'),
2139 'substances': _('Consumable substances'),
2140 'billables': _('Billable items'),
2141 'ref_data_sources': _('Reference data sources')
2142 }
2143
2144 map_list2handler = {
2145 'form_templates': gmFormWidgets.manage_form_templates,
2146 'doc_types': gmDocumentWidgets.manage_document_types,
2147 'text_expansions': gmKeywordExpansionWidgets.configure_keyword_text_expansion,
2148 'db_translations': gmI18nWidgets.manage_translations,
2149 'codes': gmCodingWidgets.browse_coded_terms,
2150 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2151 'provinces': gmAddressWidgets.manage_provinces,
2152 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins,
2153 'drugs': gmMedicationWidgets.manage_branded_drugs,
2154 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2155 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2156 'test_types': gmMeasurementWidgets.manage_measurement_types,
2157 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2158 'vaccines': gmVaccWidgets.manage_vaccines,
2159 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2160 'orgs': gmOrganizationWidgets.manage_orgs,
2161 'adr': gmAddressWidgets.manage_addresses,
2162 'substances': gmMedicationWidgets.manage_consumable_substances,
2163 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2164 'communication_channel_types': gmContactWidgets.manage_comm_channel_types,
2165 'billables': gmBillingWidgets.manage_billables,
2166 'ref_data_sources': gmCodingWidgets.browse_data_sources,
2167 'hints': gmProviderInboxWidgets.browse_dynamic_hints,
2168 }
2169
2170
2171 def edit(item):
2172 try: map_list2handler[item](parent = self)
2173 except KeyError: pass
2174 return False
2175
2176
2177 gmListWidgets.get_choices_from_list (
2178 parent = self,
2179 caption = _('Master data management'),
2180 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2181 data = master_data_lists,
2182 columns = [_('Select the list you want to manage:')],
2183 edit_callback = edit,
2184 single_selection = True,
2185 ignore_OK_button = True
2186 )
2187
2189
2190 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2191 if found:
2192 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2193 return
2194
2195 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2196 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking = False)
2197 return
2198
2199 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2200 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2201 if found:
2202 gmShellAPI.run_command_in_shell(cmd, blocking = False)
2203 return
2204
2205 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2206
2208
2209 curr_pat = gmPerson.gmCurrentPatient()
2210
2211 arriba = gmArriba.cArriba()
2212 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2213 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2214 return
2215
2216
2217 if curr_pat is None:
2218 return
2219
2220 if arriba.pdf_result is None:
2221 return
2222
2223 doc = gmDocumentWidgets.save_file_as_new_document (
2224 parent = self,
2225 filename = arriba.pdf_result,
2226 document_type = _('risk assessment')
2227 )
2228
2229 try: os.remove(arriba.pdf_result)
2230 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2231
2232 if doc is None:
2233 return
2234
2235 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2236 doc.save()
2237
2238 try:
2239 open(arriba.xml_result).close()
2240 part = doc.add_part(file = arriba.xml_result)
2241 except StandardError:
2242 _log.exception('error accessing [%s]', arriba.xml_result)
2243 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2244
2245 if part is None:
2246 return
2247
2248 part['obj_comment'] = u'XML-Daten'
2249 part['filename'] = u'arriba-result.xml'
2250 part.save()
2251
2253
2254 dbcfg = gmCfg.cCfgSQL()
2255 cmd = dbcfg.get2 (
2256 option = u'external.tools.acs_risk_calculator_cmd',
2257 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2258 bias = 'user'
2259 )
2260
2261 if cmd is None:
2262 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2263 return
2264
2265 cwd = os.path.expanduser(os.path.join('~', '.gnumed'))
2266 try:
2267 subprocess.check_call (
2268 args = (cmd,),
2269 close_fds = True,
2270 cwd = cwd
2271 )
2272 except (OSError, ValueError, subprocess.CalledProcessError):
2273 _log.exception('there was a problem executing [%s]', cmd)
2274 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2275 return
2276
2277 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2278 for pdf in pdfs:
2279 try:
2280 open(pdf).close()
2281 except:
2282 _log.exception('error accessing [%s]', pdf)
2283 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2284 continue
2285
2286 doc = gmDocumentWidgets.save_file_as_new_document (
2287 parent = self,
2288 filename = pdf,
2289 document_type = u'risk assessment'
2290 )
2291
2292 try:
2293 os.remove(pdf)
2294 except StandardError:
2295 _log.exception('cannot remove [%s]', pdf)
2296
2297 if doc is None:
2298 continue
2299 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2300 doc.save()
2301
2302 return
2303
2305 dlg = gmSnellen.cSnellenCfgDlg()
2306 if dlg.ShowModal() != wx.ID_OK:
2307 return
2308
2309 frame = gmSnellen.cSnellenChart (
2310 width = dlg.vals[0],
2311 height = dlg.vals[1],
2312 alpha = dlg.vals[2],
2313 mirr = dlg.vals[3],
2314 parent = None
2315 )
2316 frame.CentreOnScreen(wx.BOTH)
2317
2318
2319 frame.Show(True)
2320
2321
2324
2327
2330
2331
2332
2336
2339
2340
2341
2343 wx.CallAfter(self.__save_screenshot)
2344 evt.Skip()
2345
2347
2348 time.sleep(0.5)
2349
2350 rect = self.GetRect()
2351
2352
2353 if sys.platform == 'linux2':
2354 client_x, client_y = self.ClientToScreen((0, 0))
2355 border_width = client_x - rect.x
2356 title_bar_height = client_y - rect.y
2357
2358 if self.GetMenuBar():
2359 title_bar_height /= 2
2360 rect.width += (border_width * 2)
2361 rect.height += title_bar_height + border_width
2362
2363 wdc = wx.ScreenDC()
2364 mdc = wx.MemoryDC()
2365 img = wx.EmptyBitmap(rect.width, rect.height)
2366 mdc.SelectObject(img)
2367 mdc.Blit (
2368 0, 0,
2369 rect.width, rect.height,
2370 wdc,
2371 rect.x, rect.y
2372 )
2373
2374
2375 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2376 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2377 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2378
2380
2381 raise ValueError('raised ValueError to test exception handling')
2382
2384 import wx.lib.inspection
2385 wx.lib.inspection.InspectionTool().Show()
2386
2389
2392
2395
2398
2405
2409
2412
2415
2422
2427
2429 name = os.path.basename(gmLog2._logfile_name)
2430 name, ext = os.path.splitext(name)
2431 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2432 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2433
2434 dlg = wx.FileDialog (
2435 parent = self,
2436 message = _("Save current log as..."),
2437 defaultDir = new_path,
2438 defaultFile = new_name,
2439 wildcard = "%s (*.log)|*.log" % _("log files"),
2440 style = wx.SAVE
2441 )
2442 choice = dlg.ShowModal()
2443 new_name = dlg.GetPath()
2444 dlg.Destroy()
2445 if choice != wx.ID_OK:
2446 return True
2447
2448 _log.warning('syncing log file for backup to [%s]', new_name)
2449 gmLog2.flush()
2450 shutil.copy2(gmLog2._logfile_name, new_name)
2451 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2452
2455
2456
2457
2459 """This is the wx.EVT_CLOSE handler.
2460
2461 - framework still functional
2462 """
2463 _log.debug('gmTopLevelFrame.OnClose() start')
2464 self._clean_exit()
2465 self.Destroy()
2466 _log.debug('gmTopLevelFrame.OnClose() end')
2467 return True
2468
2474
2479
2487
2494
2501
2508
2518
2526
2534
2542
2550
2559
2568
2576
2593
2596
2599
2601
2602 pat = gmPerson.gmCurrentPatient()
2603 if not pat.connected:
2604 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2605 return False
2606
2607 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2608
2609 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2610 gmTools.mkdir(aDefDir)
2611
2612 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2613 dlg = wx.FileDialog (
2614 parent = self,
2615 message = _("Save patient's EMR journal as..."),
2616 defaultDir = aDefDir,
2617 defaultFile = fname,
2618 wildcard = aWildcard,
2619 style = wx.SAVE
2620 )
2621 choice = dlg.ShowModal()
2622 fname = dlg.GetPath()
2623 dlg.Destroy()
2624 if choice != wx.ID_OK:
2625 return True
2626
2627 _log.debug('exporting EMR journal to [%s]' % fname)
2628
2629 exporter = gmPatientExporter.cEMRJournalExporter()
2630
2631 wx.BeginBusyCursor()
2632 try:
2633 fname = exporter.export_to_file(filename = fname)
2634 except:
2635 wx.EndBusyCursor()
2636 gmGuiHelpers.gm_show_error (
2637 _('Error exporting patient EMR as chronological journal.'),
2638 _('EMR journal export')
2639 )
2640 raise
2641 wx.EndBusyCursor()
2642
2643 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2644
2645 return True
2646
2653
2655 curr_pat = gmPerson.gmCurrentPatient()
2656 if not curr_pat.connected:
2657 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2658 return
2659
2660 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2661 if tag is None:
2662 return
2663
2664 tag = curr_pat.add_tag(tag['pk_tag_image'])
2665 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2666 comment = wx.GetTextFromUser (
2667 message = msg,
2668 caption = _('Editing tag comment'),
2669 default_value = gmTools.coalesce(tag['comment'], u''),
2670 parent = self
2671 )
2672
2673 if comment == u'':
2674 return
2675
2676 if comment.strip() == tag['comment']:
2677 return
2678
2679 if comment == u' ':
2680 tag['comment'] = None
2681 else:
2682 tag['comment'] = comment.strip()
2683
2684 tag.save()
2685
2695
2697 curr_pat = gmPerson.gmCurrentPatient()
2698 if not curr_pat.connected:
2699 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2700 return False
2701
2702 enc = 'cp850'
2703 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2704 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2705 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2706
2709
2717
2725
2728
2735
2739
2742
2745
2748
2751
2756
2758 """Cleanup helper.
2759
2760 - should ALWAYS be called when this program is
2761 to be terminated
2762 - ANY code that should be executed before a
2763 regular shutdown should go in here
2764 - framework still functional
2765 """
2766 _log.debug('gmTopLevelFrame._clean_exit() start')
2767
2768
2769 listener = gmBackendListener.gmBackendListener()
2770 try:
2771 listener.shutdown()
2772 except:
2773 _log.exception('cannot stop backend notifications listener thread')
2774
2775
2776 if _scripting_listener is not None:
2777 try:
2778 _scripting_listener.shutdown()
2779 except:
2780 _log.exception('cannot stop scripting listener thread')
2781
2782
2783 self.clock_update_timer.Stop()
2784 gmTimer.shutdown()
2785 gmPhraseWheel.shutdown()
2786
2787
2788 for call_back in self.__pre_exit_callbacks:
2789 try:
2790 call_back()
2791 except:
2792 print "*** pre-exit callback failed ***"
2793 print call_back
2794 _log.exception('callback [%s] failed', call_back)
2795
2796
2797 gmDispatcher.send(u'application_closing')
2798
2799
2800 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2801
2802
2803 curr_width, curr_height = self.GetClientSizeTuple()
2804 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2805 dbcfg = gmCfg.cCfgSQL()
2806 dbcfg.set (
2807 option = 'main.window.width',
2808 value = curr_width,
2809 workplace = gmSurgery.gmCurrentPractice().active_workplace
2810 )
2811 dbcfg.set (
2812 option = 'main.window.height',
2813 value = curr_height,
2814 workplace = gmSurgery.gmCurrentPractice().active_workplace
2815 )
2816
2817 if _cfg.get(option = 'debug'):
2818 print '---=== GNUmed shutdown ===---'
2819 try:
2820 print _('You have to manually close this window to finalize shutting down GNUmed.')
2821 print _('This is so that you can inspect the console output at your leisure.')
2822 except UnicodeEncodeError:
2823 print 'You have to manually close this window to finalize shutting down GNUmed.'
2824 print 'This is so that you can inspect the console output at your leisure.'
2825 print '---=== GNUmed shutdown ===---'
2826
2827
2828 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2829
2830
2831 import threading
2832 _log.debug("%s active threads", threading.activeCount())
2833 for t in threading.enumerate():
2834 _log.debug('thread %s', t)
2835
2836 _log.debug('gmTopLevelFrame._clean_exit() end')
2837
2838
2839
2841
2842 if _cfg.get(option = 'slave'):
2843 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2844 _cfg.get(option = 'slave personality'),
2845 _cfg.get(option = 'xml-rpc port')
2846 )
2847 else:
2848 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2849
2851 """Update title of main window based on template.
2852
2853 This gives nice tooltips on iconified GNUmed instances.
2854
2855 User research indicates that in the title bar people want
2856 the date of birth, not the age, so please stick to this
2857 convention.
2858 """
2859 args = {}
2860
2861 pat = gmPerson.gmCurrentPatient()
2862 if pat.connected:
2863 args['pat'] = u'%s %s %s (%s) #%d' % (
2864 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2865 pat['firstnames'],
2866 pat['lastnames'],
2867 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2868 pat['pk_identity']
2869 )
2870 else:
2871 args['pat'] = _('no patient')
2872
2873 args['prov'] = u'%s%s.%s' % (
2874 gmTools.coalesce(_provider['title'], u'', u'%s '),
2875 _provider['firstnames'][:1],
2876 _provider['lastnames']
2877 )
2878
2879 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2880
2881 self.SetTitle(self.__title_template % args)
2882
2883
2885 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2886 sb.SetStatusWidths([-1, 225])
2887
2888 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2889 self._cb_update_clock()
2890
2891 self.clock_update_timer.Start(milliseconds = 1000)
2892
2894 """Displays date and local time in the second slot of the status bar"""
2895 t = time.localtime(time.time())
2896 st = time.strftime('%c', t).decode(gmI18N.get_encoding(), 'replace')
2897 self.SetStatusText(st, 1)
2898
2900 """Lock GNUmed client against unauthorized access"""
2901
2902
2903
2904 return
2905
2907 """Unlock the main notebook widgets
2908 As long as we are not logged into the database backend,
2909 all pages but the 'login' page of the main notebook widget
2910 are locked; i.e. not accessible by the user
2911 """
2912
2913
2914
2915
2916
2917 return
2918
2920 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2921
2923
2925
2926 self.__starting_up = True
2927
2928 gmExceptionHandlingWidgets.install_wx_exception_handler()
2929 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2930
2931
2932
2933
2934 self.SetAppName(u'gnumed')
2935 self.SetVendorName(u'The GNUmed Development Community.')
2936 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2937 paths.init_paths(wx = wx, app_name = u'gnumed')
2938
2939 if not self.__setup_prefs_file():
2940 return False
2941
2942 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2943
2944 self.__guibroker = gmGuiBroker.GuiBroker()
2945 self.__setup_platform()
2946
2947 if not self.__establish_backend_connection():
2948 return False
2949
2950 if not _cfg.get(option = 'skip-update-check'):
2951 self.__check_for_updates()
2952
2953 if _cfg.get(option = 'slave'):
2954 if not self.__setup_scripting_listener():
2955 return False
2956
2957
2958 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2959 frame.CentreOnScreen(wx.BOTH)
2960 self.SetTopWindow(frame)
2961 frame.Show(True)
2962
2963 if _cfg.get(option = 'debug'):
2964 self.RedirectStdio()
2965 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2966
2967
2968 print '---=== GNUmed startup ===---'
2969 print _('redirecting STDOUT/STDERR to this log window')
2970 print '---=== GNUmed startup ===---'
2971
2972 self.__setup_user_activity_timer()
2973 self.__register_events()
2974
2975 wx.CallAfter(self._do_after_init)
2976
2977 return True
2978
2980 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2981
2982 - after destroying all application windows and controls
2983 - before wx.Windows internal cleanup
2984 """
2985 _log.debug('gmApp.OnExit() start')
2986
2987 self.__shutdown_user_activity_timer()
2988
2989 if _cfg.get(option = 'debug'):
2990 self.RestoreStdio()
2991 sys.stdin = sys.__stdin__
2992 sys.stdout = sys.__stdout__
2993 sys.stderr = sys.__stderr__
2994
2995 _log.debug('gmApp.OnExit() end')
2996
2998 wx.Bell()
2999 wx.Bell()
3000 wx.Bell()
3001 _log.warning('unhandled event detected: QUERY_END_SESSION')
3002 _log.info('we should be saving ourselves from here')
3003 gmLog2.flush()
3004 print "unhandled event detected: QUERY_END_SESSION"
3005
3007 wx.Bell()
3008 wx.Bell()
3009 wx.Bell()
3010 _log.warning('unhandled event detected: END_SESSION')
3011 gmLog2.flush()
3012 print "unhandled event detected: END_SESSION"
3013
3024
3026 self.user_activity_detected = True
3027 evt.Skip()
3028
3030
3031 if self.user_activity_detected:
3032 self.elapsed_inactivity_slices = 0
3033 self.user_activity_detected = False
3034 self.elapsed_inactivity_slices += 1
3035 else:
3036 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
3037
3038 pass
3039
3040 self.user_activity_timer.Start(oneShot = True)
3041
3042
3043
3045 try:
3046 kwargs['originated_in_database']
3047 print '==> got notification from database "%s":' % kwargs['signal']
3048 except KeyError:
3049 print '==> received signal from client: "%s"' % kwargs['signal']
3050
3051 del kwargs['signal']
3052 for key in kwargs.keys():
3053 print ' [%s]: %s' % (key, kwargs[key])
3054
3061
3063 self.user_activity_detected = True
3064 self.elapsed_inactivity_slices = 0
3065
3066 self.max_user_inactivity_slices = 15
3067 self.user_activity_timer = gmTimer.cTimer (
3068 callback = self._on_user_activity_timer_expired,
3069 delay = 2000
3070 )
3071 self.user_activity_timer.Start(oneShot=True)
3072
3074 try:
3075 self.user_activity_timer.Stop()
3076 del self.user_activity_timer
3077 except:
3078 pass
3079
3081 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
3082 wx.EVT_END_SESSION(self, self._on_end_session)
3083
3084
3085
3086
3087
3088 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
3089
3090 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
3091 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
3092
3093 if _cfg.get(option = 'debug'):
3094 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
3095 _log.debug('connected signal monitor')
3096
3112
3114 """Handle all the database related tasks necessary for startup."""
3115
3116
3117 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
3118
3119 from Gnumed.wxpython import gmAuthWidgets
3120 connected = gmAuthWidgets.connect_to_database (
3121 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
3122 require_version = not override
3123 )
3124 if not connected:
3125 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
3126 return False
3127
3128
3129 try:
3130 global _provider
3131 _provider = gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
3132 except ValueError:
3133 account = gmPG2.get_current_user()
3134 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
3135 msg = _(
3136 'The database account [%s] cannot be used as a\n'
3137 'staff member login for GNUmed. There was an\n'
3138 'error retrieving staff details for it.\n\n'
3139 'Please ask your administrator for help.\n'
3140 ) % account
3141 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
3142 return False
3143
3144
3145 tmp = '%s%s %s (%s = %s)' % (
3146 gmTools.coalesce(_provider['title'], ''),
3147 _provider['firstnames'],
3148 _provider['lastnames'],
3149 _provider['short_alias'],
3150 _provider['db_user']
3151 )
3152 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
3153
3154
3155 surgery = gmSurgery.gmCurrentPractice()
3156 msg = surgery.db_logon_banner
3157 if msg.strip() != u'':
3158
3159 login = gmPG2.get_default_login()
3160 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
3161 login.database,
3162 gmTools.coalesce(login.host, u'localhost')
3163 ))
3164 msg = auth + msg + u'\n\n'
3165
3166 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3167 None,
3168
3169 -1,
3170 caption = _('Verifying database'),
3171 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3172 button_defs = [
3173 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3174 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3175 ]
3176 )
3177 go_on = dlg.ShowModal()
3178 dlg.Destroy()
3179 if go_on != wx.ID_YES:
3180 _log.info('user decided to not connect to this database')
3181 return False
3182
3183
3184 self.__check_db_lang()
3185
3186 return True
3187
3189 """Setup access to a config file for storing preferences."""
3190
3191 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3192
3193 candidates = []
3194 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3195 if explicit_file is not None:
3196 candidates.append(explicit_file)
3197
3198 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3199 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3200 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3201
3202 prefs_file = None
3203 for candidate in candidates:
3204 try:
3205 open(candidate, 'a+').close()
3206 prefs_file = candidate
3207 break
3208 except IOError:
3209 continue
3210
3211 if prefs_file is None:
3212 msg = _(
3213 'Cannot find configuration file in any of:\n'
3214 '\n'
3215 ' %s\n'
3216 'You may need to use the comand line option\n'
3217 '\n'
3218 ' --conf-file=<FILE>'
3219 ) % '\n '.join(candidates)
3220 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3221 return False
3222
3223 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3224 _log.info('user preferences file: %s', prefs_file)
3225
3226 return True
3227
3229
3230 from socket import error as SocketError
3231 from Gnumed.pycommon import gmScriptingListener
3232 from Gnumed.wxpython import gmMacro
3233
3234 slave_personality = gmTools.coalesce (
3235 _cfg.get (
3236 group = u'workplace',
3237 option = u'slave personality',
3238 source_order = [
3239 ('explicit', 'return'),
3240 ('workbase', 'return'),
3241 ('user', 'return'),
3242 ('system', 'return')
3243 ]
3244 ),
3245 u'gnumed-client'
3246 )
3247 _cfg.set_option(option = 'slave personality', value = slave_personality)
3248
3249
3250 port = int (
3251 gmTools.coalesce (
3252 _cfg.get (
3253 group = u'workplace',
3254 option = u'xml-rpc port',
3255 source_order = [
3256 ('explicit', 'return'),
3257 ('workbase', 'return'),
3258 ('user', 'return'),
3259 ('system', 'return')
3260 ]
3261 ),
3262 9999
3263 )
3264 )
3265 _cfg.set_option(option = 'xml-rpc port', value = port)
3266
3267 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3268 global _scripting_listener
3269 try:
3270 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3271 except SocketError, e:
3272 _log.exception('cannot start GNUmed XML-RPC server')
3273 gmGuiHelpers.gm_show_error (
3274 aMessage = (
3275 'Cannot start the GNUmed server:\n'
3276 '\n'
3277 ' [%s]'
3278 ) % e,
3279 aTitle = _('GNUmed startup')
3280 )
3281 return False
3282
3283 return True
3284
3305
3307 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3308 _log.warning("system locale is undefined (probably meaning 'C')")
3309 return True
3310
3311
3312 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3313 db_lang = rows[0]['lang']
3314
3315 if db_lang is None:
3316 _log.debug("database locale currently not set")
3317 msg = _(
3318 "There is no language selected in the database for user [%s].\n"
3319 "Your system language is currently set to [%s].\n\n"
3320 "Do you want to set the database language to '%s' ?\n\n"
3321 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3322 checkbox_msg = _('Remember to ignore missing language')
3323 else:
3324 _log.debug("current database locale: [%s]" % db_lang)
3325 msg = _(
3326 "The currently selected database language ('%s') does\n"
3327 "not match the current system language ('%s').\n"
3328 "\n"
3329 "Do you want to set the database language to '%s' ?\n"
3330 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3331 checkbox_msg = _('Remember to ignore language mismatch')
3332
3333
3334 if db_lang == gmI18N.system_locale_level['full']:
3335 _log.debug('Database locale (%s) up to date.' % db_lang)
3336 return True
3337 if db_lang == gmI18N.system_locale_level['country']:
3338 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3339 return True
3340 if db_lang == gmI18N.system_locale_level['language']:
3341 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3342 return True
3343
3344 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3345
3346
3347 ignored_sys_lang = _cfg.get (
3348 group = u'backend',
3349 option = u'ignored mismatching system locale',
3350 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3351 )
3352
3353
3354 if gmI18N.system_locale == ignored_sys_lang:
3355 _log.info('configured to ignore system-to-database locale mismatch')
3356 return True
3357
3358
3359 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3360 None,
3361 -1,
3362 caption = _('Checking database language settings'),
3363 question = msg,
3364 button_defs = [
3365 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3366 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3367 ],
3368 show_checkbox = True,
3369 checkbox_msg = checkbox_msg,
3370 checkbox_tooltip = _(
3371 'Checking this will make GNUmed remember your decision\n'
3372 'until the system language is changed.\n'
3373 '\n'
3374 'You can also reactivate this inquiry by removing the\n'
3375 'corresponding "ignore" option from the configuration file\n'
3376 '\n'
3377 ' [%s]'
3378 ) % _cfg.get(option = 'user_preferences_file')
3379 )
3380 decision = dlg.ShowModal()
3381 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3382 dlg.Destroy()
3383
3384 if decision == wx.ID_NO:
3385 if not remember_ignoring_problem:
3386 return True
3387 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3388 gmCfg2.set_option_in_INI_file (
3389 filename = _cfg.get(option = 'user_preferences_file'),
3390 group = 'backend',
3391 option = 'ignored mismatching system locale',
3392 value = gmI18N.system_locale
3393 )
3394 return True
3395
3396
3397 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3398 if len(lang) > 0:
3399
3400
3401 rows, idx = gmPG2.run_rw_queries (
3402 link_obj = None,
3403 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3404 return_data = True
3405 )
3406 if rows[0][0]:
3407 _log.debug("Successfully set database language to [%s]." % lang)
3408 else:
3409 _log.error('Cannot set database language to [%s].' % lang)
3410 continue
3411 return True
3412
3413
3414 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3415 gmPG2.run_rw_queries(queries = [{
3416 'cmd': u'select i18n.force_curr_lang(%s)',
3417 'args': [gmI18N.system_locale_level['country']]
3418 }])
3419
3420 return True
3421
3423 try:
3424 kwargs['originated_in_database']
3425 print '==> got notification from database "%s":' % kwargs['signal']
3426 except KeyError:
3427 print '==> received signal from client: "%s"' % kwargs['signal']
3428
3429 del kwargs['signal']
3430 for key in kwargs.keys():
3431
3432 try: print ' [%s]: %s' % (key, kwargs[key])
3433 except: print 'cannot print signal information'
3434
3438
3449
3451
3452 if _cfg.get(option = 'debug'):
3453 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3454 _log.debug('gmDispatcher signal monitor activated')
3455
3456 setup_safe_wxEndBusyCursor()
3457
3458 wx.InitAllImageHandlers()
3459
3460
3461
3462 app = gmApp(redirect = False, clearSigInt = False)
3463 app.MainLoop()
3464
3465
3466
3467 if __name__ == '__main__':
3468
3469 from GNUmed.pycommon import gmI18N
3470 gmI18N.activate_locale()
3471 gmI18N.install_domain()
3472
3473 _log.info('Starting up as main module.')
3474 main()
3475
3476
3477