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
78
79 try:
80 _('dummy-no-need-to-translate-but-make-epydoc-happy')
81 except NameError:
82 _ = lambda x:x
83
84 _cfg = gmCfg2.gmCfgData()
85 _provider = None
86 _scripting_listener = None
87 _original_wxEndBusyCursor = None
88
89 _log = logging.getLogger('gm.main')
90 _log.info(__version__)
91 _log.info('wxPython GUI framework: %s %s' % (wx.VERSION_STRING, wx.PlatformInfo))
92
93
95 """GNUmed client's main windows frame.
96
97 This is where it all happens. Avoid popping up any other windows.
98 Most user interaction should happen to and from widgets within this frame
99 """
100
101 - def __init__(self, parent, id, title, size=wx.DefaultSize):
102 """You'll have to browse the source to understand what the constructor does
103 """
104 wx.Frame.__init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE)
105
106 self.__setup_font()
107
108 self.__gb = gmGuiBroker.GuiBroker()
109 self.__pre_exit_callbacks = []
110 self.bar_width = -1
111 self.menu_id2plugin = {}
112
113 _log.info('workplace is >>>%s<<<', gmSurgery.gmCurrentPractice().active_workplace)
114
115 self.__setup_main_menu()
116 self.setup_statusbar()
117 self.SetStatusText(_('You are logged in as %s%s.%s (%s). DB account <%s>.') % (
118 gmTools.coalesce(_provider['title'], ''),
119 _provider['firstnames'][:1],
120 _provider['lastnames'],
121 _provider['short_alias'],
122 _provider['db_user']
123 ))
124
125 self.__set_window_title_template()
126 self.__update_window_title()
127
128
129
130
131
132 self.SetIcon(gmTools.get_icon(wx = wx))
133
134 self.__register_events()
135
136 self.LayoutMgr = gmHorstSpace.cHorstSpaceLayoutMgr(self, -1)
137 self.vbox = wx.BoxSizer(wx.VERTICAL)
138 self.vbox.Add(self.LayoutMgr, 10, wx.EXPAND | wx.ALL, 1)
139
140 self.SetAutoLayout(True)
141 self.SetSizerAndFit(self.vbox)
142
143
144
145
146
147 self.__set_GUI_size()
148
149
151
152 font = self.GetFont()
153 _log.debug('system default font is [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
154
155 desired_font_face = _cfg.get (
156 group = u'workplace',
157 option = u'client font',
158 source_order = [
159 ('explicit', 'return'),
160 ('workbase', 'return'),
161 ('local', 'return'),
162 ('user', 'return'),
163 ('system', 'return')
164 ]
165 )
166
167 fonts2try = []
168 if desired_font_face is not None:
169 _log.info('client is configured to use font [%s]', desired_font_face)
170 fonts2try.append(desired_font_face)
171
172 if wx.Platform == '__WXMSW__':
173 sane_font_face = u'DejaVu Sans'
174 _log.info('MS Windows: appending fallback font candidate [%s]', sane_font_face)
175 fonts2try.append(sane_font_face)
176
177 if len(fonts2try) == 0:
178 return
179
180 for font_face in fonts2try:
181 success = font.SetFaceName(font_face)
182 if success:
183 self.SetFont(font)
184 _log.debug('switched font to [%s] (%s)', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc())
185 return
186 font = self.GetFont()
187 _log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face)
188
189 return
190
192 """Try to get previous window size from backend."""
193
194 cfg = gmCfg.cCfgSQL()
195
196
197 width = int(cfg.get2 (
198 option = 'main.window.width',
199 workplace = gmSurgery.gmCurrentPractice().active_workplace,
200 bias = 'workplace',
201 default = 800
202 ))
203
204
205 height = int(cfg.get2 (
206 option = 'main.window.height',
207 workplace = gmSurgery.gmCurrentPractice().active_workplace,
208 bias = 'workplace',
209 default = 600
210 ))
211
212 dw = wx.DisplaySize()[0]
213 dh = wx.DisplaySize()[1]
214
215 _log.info('display size: %s:%s' % (wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X), wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)))
216 _log.debug('display size: %s:%s %s mm', dw, dh, str(wx.DisplaySizeMM()))
217 _log.debug('previous GUI size [%s:%s]', width, height)
218
219
220 if width > dw:
221 _log.debug('adjusting GUI width from %s to %s', width, dw)
222 width = dw
223
224 if height > dh:
225 _log.debug('adjusting GUI height from %s to %s', height, dh)
226 height = dh
227
228
229 if width < 100:
230 _log.debug('adjusting GUI width to minimum of 100 pixel')
231 width = 100
232 if height < 100:
233 _log.debug('adjusting GUI height to minimum of 100 pixel')
234 height = 100
235
236 _log.info('setting GUI to size [%s:%s]', width, height)
237
238 self.SetClientSize(wx.Size(width, height))
239
241 """Create the main menu entries.
242
243 Individual entries are farmed out to the modules.
244
245 menu item template:
246
247 item = menu_emr_edit.Append(-1, _(''), _(''))
248 self.Bind(wx.EVT_MENU, self__on_, item)
249 """
250 global wx
251 self.mainmenu = wx.MenuBar()
252 self.__gb['main.mainmenu'] = self.mainmenu
253
254
255 menu_gnumed = wx.Menu()
256
257 self.menu_plugins = wx.Menu()
258 menu_gnumed.AppendMenu(wx.NewId(), _('&Go to plugin ...'), self.menu_plugins)
259
260 ID = wx.NewId()
261 menu_gnumed.Append(ID, _('Check for updates'), _('Check for new releases of the GNUmed client.'))
262 wx.EVT_MENU(self, ID, self.__on_check_for_updates)
263
264 item = menu_gnumed.Append(-1, _('Announce downtime'), _('Announce database maintenance downtime to all connected clients.'))
265 self.Bind(wx.EVT_MENU, self.__on_announce_maintenance, item)
266
267
268 menu_gnumed.AppendSeparator()
269
270
271 menu_config = wx.Menu()
272
273 item = menu_config.Append(-1, _('List configuration'), _('List all configuration items stored in the database.'))
274 self.Bind(wx.EVT_MENU, self.__on_list_configuration, item)
275
276
277 menu_cfg_db = wx.Menu()
278
279 ID = wx.NewId()
280 menu_cfg_db.Append(ID, _('Language'), _('Configure the database language'))
281 wx.EVT_MENU(self, ID, self.__on_configure_db_lang)
282
283 ID = wx.NewId()
284 menu_cfg_db.Append(ID, _('Welcome message'), _('Configure the database welcome message (all users).'))
285 wx.EVT_MENU(self, ID, self.__on_configure_db_welcome)
286
287 menu_config.AppendMenu(wx.NewId(), _('Database ...'), menu_cfg_db)
288
289
290 menu_cfg_client = wx.Menu()
291
292 ID = wx.NewId()
293 menu_cfg_client.Append(ID, _('Export chunk size'), _('Configure the chunk size used when exporting BLOBs from the database.'))
294 wx.EVT_MENU(self, ID, self.__on_configure_export_chunk_size)
295
296 item = menu_cfg_client.Append(-1, _('Email address'), _('The email address of the user for sending bug reports, etc.'))
297 self.Bind(wx.EVT_MENU, self.__on_configure_user_email, item)
298
299 menu_config.AppendMenu(wx.NewId(), _('Client parameters ...'), menu_cfg_client)
300
301
302 menu_cfg_ui = wx.Menu()
303
304
305 menu_cfg_doc = wx.Menu()
306
307 ID = wx.NewId()
308 menu_cfg_doc.Append(ID, _('Review dialog'), _('Configure review dialog after document display.'))
309 wx.EVT_MENU(self, ID, self.__on_configure_doc_review_dialog)
310
311 ID = wx.NewId()
312 menu_cfg_doc.Append(ID, _('UUID display'), _('Configure unique ID dialog on document import.'))
313 wx.EVT_MENU(self, ID, self.__on_configure_doc_uuid_dialog)
314
315 ID = wx.NewId()
316 menu_cfg_doc.Append(ID, _('Empty documents'), _('Whether to allow saving documents without parts.'))
317 wx.EVT_MENU(self, ID, self.__on_configure_partless_docs)
318
319 item = menu_cfg_doc.Append(-1, _('Generate UUID'), _('Whether to generate UUIDs for new documents.'))
320 self.Bind(wx.EVT_MENU, self.__on_configure_generate_doc_uuid, item)
321
322 menu_cfg_ui.AppendMenu(wx.NewId(), _('Document handling ...'), menu_cfg_doc)
323
324
325 menu_cfg_update = wx.Menu()
326
327 ID = wx.NewId()
328 menu_cfg_update.Append(ID, _('Auto-check'), _('Whether to auto-check for updates at startup.'))
329 wx.EVT_MENU(self, ID, self.__on_configure_update_check)
330
331 ID = wx.NewId()
332 menu_cfg_update.Append(ID, _('Check scope'), _('When checking for updates, consider latest branch, too ?'))
333 wx.EVT_MENU(self, ID, self.__on_configure_update_check_scope)
334
335 ID = wx.NewId()
336 menu_cfg_update.Append(ID, _('URL'), _('The URL to retrieve version information from.'))
337 wx.EVT_MENU(self, ID, self.__on_configure_update_url)
338
339 menu_cfg_ui.AppendMenu(wx.NewId(), _('Update handling ...'), menu_cfg_update)
340
341
342 menu_cfg_pat_search = wx.Menu()
343
344 ID = wx.NewId()
345 menu_cfg_pat_search.Append(ID, _('Birthday reminder'), _('Configure birthday reminder proximity interval.'))
346 wx.EVT_MENU(self, ID, self.__on_configure_dob_reminder_proximity)
347
348 ID = wx.NewId()
349 menu_cfg_pat_search.Append(ID, _('Immediate source activation'), _('Configure immediate activation of single external person.'))
350 wx.EVT_MENU(self, ID, self.__on_configure_quick_pat_search)
351
352 ID = wx.NewId()
353 menu_cfg_pat_search.Append(ID, _('Initial plugin'), _('Configure which plugin to show right after person activation.'))
354 wx.EVT_MENU(self, ID, self.__on_configure_initial_pat_plugin)
355
356 item = menu_cfg_pat_search.Append(-1, _('Default region'), _('Configure the default province/region/state for person creation.'))
357 self.Bind(wx.EVT_MENU, self.__on_cfg_default_region, item)
358
359 item = menu_cfg_pat_search.Append(-1, _('Default country'), _('Configure the default country for person creation.'))
360 self.Bind(wx.EVT_MENU, self.__on_cfg_default_country, item)
361
362 menu_cfg_ui.AppendMenu(wx.NewId(), _('Person ...'), menu_cfg_pat_search)
363
364
365 menu_cfg_soap_editing = wx.Menu()
366
367 ID = wx.NewId()
368 menu_cfg_soap_editing.Append(ID, _('Multiple new episodes'), _('Configure opening multiple new episodes on a patient at once.'))
369 wx.EVT_MENU(self, ID, self.__on_allow_multiple_new_episodes)
370
371 item = menu_cfg_soap_editing.Append(-1, _('Auto-open editors'), _('Configure auto-opening editors for recent problems.'))
372 self.Bind(wx.EVT_MENU, self.__on_allow_auto_open_episodes, item)
373
374 menu_cfg_ui.AppendMenu(wx.NewId(), _('Progress notes handling ...'), menu_cfg_soap_editing)
375
376 menu_config.AppendMenu(wx.NewId(), _('User interface ...'), menu_cfg_ui)
377
378
379 menu_cfg_ext_tools = wx.Menu()
380
381
382
383
384
385 item = menu_cfg_ext_tools.Append(-1, _('MI/stroke risk calc cmd'), _('Set the command to start the CV risk calculator.'))
386 self.Bind(wx.EVT_MENU, self.__on_configure_acs_risk_calculator_cmd, item)
387
388 ID = wx.NewId()
389 menu_cfg_ext_tools.Append(ID, _('OOo startup time'), _('Set the time to wait for OpenOffice to settle after startup.'))
390 wx.EVT_MENU(self, ID, self.__on_configure_ooo_settle_time)
391
392 item = menu_cfg_ext_tools.Append(-1, _('Measurements URL'), _('URL for measurements encyclopedia.'))
393 self.Bind(wx.EVT_MENU, self.__on_configure_measurements_url, item)
394
395 item = menu_cfg_ext_tools.Append(-1, _('Drug data source'), _('Select the drug data source.'))
396 self.Bind(wx.EVT_MENU, self.__on_configure_drug_data_source, item)
397
398
399
400
401 item = menu_cfg_ext_tools.Append(-1, _('ADR URL'), _('URL for reporting Adverse Drug Reactions.'))
402 self.Bind(wx.EVT_MENU, self.__on_configure_adr_url, item)
403
404 item = menu_cfg_ext_tools.Append(-1, _('vaccADR URL'), _('URL for reporting Adverse Drug Reactions to *vaccines*.'))
405 self.Bind(wx.EVT_MENU, self.__on_configure_vaccine_adr_url, item)
406
407 item = menu_cfg_ext_tools.Append(-1, _('Vacc plans URL'), _('URL for vaccination plans.'))
408 self.Bind(wx.EVT_MENU, self.__on_configure_vaccination_plans_url, item)
409
410 item = menu_cfg_ext_tools.Append(-1, _('Visual SOAP editor'), _('Set the command for calling the visual progress note editor.'))
411 self.Bind(wx.EVT_MENU, self.__on_configure_visual_soap_cmd, item)
412
413 menu_config.AppendMenu(wx.NewId(), _('External tools ...'), menu_cfg_ext_tools)
414
415
416 menu_cfg_emr = wx.Menu()
417
418 item = menu_cfg_emr.Append(-1, _('Medication list template'), _('Select the template for printing a medication list.'))
419 self.Bind(wx.EVT_MENU, self.__on_cfg_medication_list_template, item)
420
421 item = menu_cfg_emr.Append(-1, _('Primary doctor'), _('Select the primary doctor to fall back to for patients without one.'))
422 self.Bind(wx.EVT_MENU, self.__on_cfg_fallback_primary_provider, item)
423
424
425 menu_cfg_encounter = wx.Menu()
426
427 ID = wx.NewId()
428 menu_cfg_encounter.Append(ID, _('Edit before patient change'), _('Edit encounter details before change of patient.'))
429 wx.EVT_MENU(self, ID, self.__on_cfg_enc_pat_change)
430
431 ID = wx.NewId()
432 menu_cfg_encounter.Append(ID, _('Minimum duration'), _('Minimum duration of an encounter.'))
433 wx.EVT_MENU(self, ID, self.__on_cfg_enc_min_ttl)
434
435 ID = wx.NewId()
436 menu_cfg_encounter.Append(ID, _('Maximum duration'), _('Maximum duration of an encounter.'))
437 wx.EVT_MENU(self, ID, self.__on_cfg_enc_max_ttl)
438
439 ID = wx.NewId()
440 menu_cfg_encounter.Append(ID, _('Minimum empty age'), _('Minimum age of an empty encounter before considering for deletion.'))
441 wx.EVT_MENU(self, ID, self.__on_cfg_enc_empty_ttl)
442
443 ID = wx.NewId()
444 menu_cfg_encounter.Append(ID, _('Default type'), _('Default type for new encounters.'))
445 wx.EVT_MENU(self, ID, self.__on_cfg_enc_default_type)
446
447 menu_cfg_emr.AppendMenu(wx.NewId(), _('Encounter ...'), menu_cfg_encounter)
448
449
450 menu_cfg_episode = wx.Menu()
451
452 ID = wx.NewId()
453 menu_cfg_episode.Append(ID, _('Dormancy'), _('Maximum length of dormancy after which an episode will be considered closed.'))
454 wx.EVT_MENU(self, ID, self.__on_cfg_epi_ttl)
455
456 menu_cfg_emr.AppendMenu(wx.NewId(), _('Episode ...'), menu_cfg_episode)
457 menu_config.AppendMenu(wx.NewId(), _('EMR ...'), menu_cfg_emr)
458 menu_gnumed.AppendMenu(wx.NewId(), _('Preferences ...'), menu_config)
459
460
461 menu_master_data = wx.Menu()
462
463 item = menu_master_data.Append(-1, _('Manage lists'), _('Manage various lists of master data.'))
464 self.Bind(wx.EVT_MENU, self.__on_manage_master_data, item)
465
466 item = menu_master_data.Append(-1, _('Install data packs'), _('Install reference data from data packs.'))
467 self.Bind(wx.EVT_MENU, self.__on_install_data_packs, item)
468
469 item = menu_master_data.Append(-1, _('Update ATC'), _('Install ATC reference data.'))
470 self.Bind(wx.EVT_MENU, self.__on_update_atc, item)
471
472
473
474
475 item = menu_master_data.Append(-1, _('Create fake vaccines'), _('Re-create fake generic vaccines.'))
476 self.Bind(wx.EVT_MENU, self.__on_generate_vaccines, item)
477
478 menu_gnumed.AppendMenu(wx.NewId(), _('&Master data ...'), menu_master_data)
479
480
481 menu_users = wx.Menu()
482
483 item = menu_users.Append(-1, _('&Add user'), _('Add a new GNUmed user'))
484 self.Bind(wx.EVT_MENU, self.__on_add_new_staff, item)
485
486 item = menu_users.Append(-1, _('&Edit users'), _('Edit the list of GNUmed users'))
487 self.Bind(wx.EVT_MENU, self.__on_edit_staff_list, item)
488
489 item = menu_users.Append(-1, _('&Change DB owner PWD'), _('Change the password of the GNUmed database owner'))
490 self.Bind(wx.EVT_MENU, self.__on_edit_gmdbowner_password, item)
491
492 menu_gnumed.AppendMenu(wx.NewId(), _('&Users ...'), menu_users)
493
494
495 menu_gnumed.AppendSeparator()
496
497 item = menu_gnumed.Append(wx.ID_EXIT, _('E&xit\tAlt-X'), _('Close this GNUmed client.'))
498 self.Bind(wx.EVT_MENU, self.__on_exit_gnumed, item)
499
500 self.mainmenu.Append(menu_gnumed, '&GNUmed')
501
502
503 menu_person = wx.Menu()
504
505 ID_CREATE_PATIENT = wx.NewId()
506 menu_person.Append(ID_CREATE_PATIENT, _('&Register person'), _("Register a new person with GNUmed"))
507 wx.EVT_MENU(self, ID_CREATE_PATIENT, self.__on_create_new_patient)
508
509 ID_LOAD_EXT_PAT = wx.NewId()
510 menu_person.Append(ID_LOAD_EXT_PAT, _('&Load external'), _('Load and possibly create person from an external source.'))
511 wx.EVT_MENU(self, ID_LOAD_EXT_PAT, self.__on_load_external_patient)
512
513 item = menu_person.Append(-1, _('Add &tag'), _('Add a text/image tag to this person.'))
514 self.Bind(wx.EVT_MENU, self.__on_add_tag2person, item)
515
516 ID_DEL_PAT = wx.NewId()
517 menu_person.Append(ID_DEL_PAT, _('Deactivate record'), _('Deactivate (exclude from search) person record in database.'))
518 wx.EVT_MENU(self, ID_DEL_PAT, self.__on_delete_patient)
519
520 item = menu_person.Append(-1, _('&Merge persons'), _('Merge two persons into one.'))
521 self.Bind(wx.EVT_MENU, self.__on_merge_patients, item)
522
523 menu_person.AppendSeparator()
524
525 ID_ENLIST_PATIENT_AS_STAFF = wx.NewId()
526 menu_person.Append(ID_ENLIST_PATIENT_AS_STAFF, _('Enlist as user'), _('Enlist current person as GNUmed user'))
527 wx.EVT_MENU(self, ID_ENLIST_PATIENT_AS_STAFF, self.__on_enlist_patient_as_staff)
528
529
530 ID = wx.NewId()
531 menu_person.Append(ID, _('Export to GDT'), _('Export demographics of currently active person into GDT file.'))
532 wx.EVT_MENU(self, ID, self.__on_export_as_gdt)
533
534 menu_person.AppendSeparator()
535
536 self.mainmenu.Append(menu_person, '&Person')
537 self.__gb['main.patientmenu'] = menu_person
538
539
540 menu_emr = wx.Menu()
541
542
543 menu_emr_edit = wx.Menu()
544
545 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'))
546 self.Bind(wx.EVT_MENU, self.__on_add_health_issue, item)
547
548 item = menu_emr_edit.Append(-1, _('&Episode'), _('Add an episode of illness to the EMR of the active patient'))
549 self.Bind(wx.EVT_MENU, self.__on_add_episode, item)
550
551 item = menu_emr_edit.Append(-1, _('&Medication'), _('Add medication / substance use entry.'))
552 self.Bind(wx.EVT_MENU, self.__on_add_medication, item)
553
554 item = menu_emr_edit.Append(-1, _('&Allergies'), _('Manage documentation of allergies for the current patient.'))
555 self.Bind(wx.EVT_MENU, self.__on_manage_allergies, item)
556
557 item = menu_emr_edit.Append(-1, _('&Occupation'), _('Edit occupation details for the current patient.'))
558 self.Bind(wx.EVT_MENU, self.__on_edit_occupation, item)
559
560 item = menu_emr_edit.Append(-1, _('&Hospitalizations'), _('Manage hospital stays.'))
561 self.Bind(wx.EVT_MENU, self.__on_manage_hospital_stays, item)
562
563 item = menu_emr_edit.Append(-1, _('&Procedures'), _('Manage procedures performed on the patient.'))
564 self.Bind(wx.EVT_MENU, self.__on_manage_performed_procedures, item)
565
566 item = menu_emr_edit.Append(-1, _('&Measurements'), _('Add (a) measurement result(s) for the current patient.'))
567 self.Bind(wx.EVT_MENU, self.__on_add_measurement, item)
568
569 item = menu_emr_edit.Append(-1, _('&Vaccinations'), _('Add (a) vaccination(s) for the current patient.'))
570 self.Bind(wx.EVT_MENU, self.__on_add_vaccination, item)
571
572 item = menu_emr_edit.Append(-1, _('&Family history (FHx)'), _('Manage family history.'))
573 self.Bind(wx.EVT_MENU, self.__on_manage_fhx, item)
574
575 item = menu_emr_edit.Append(-1, _('&Encounters'), _('List all encounters including empty ones.'))
576 self.Bind(wx.EVT_MENU, self.__on_list_encounters, item)
577
578 menu_emr.AppendMenu(wx.NewId(), _('&Add / Edit ...'), menu_emr_edit)
579
580
581 item = menu_emr.Append(-1, _('Search this EMR'), _('Search for data in the EMR of the active patient'))
582 self.Bind(wx.EVT_MENU, self.__on_search_emr, item)
583
584 item = menu_emr.Append(-1, _('Search all EMRs'), _('Search for data across the EMRs of all patients'))
585 self.Bind(wx.EVT_MENU, self.__on_search_across_emrs, item)
586
587 item = menu_emr.Append(-1, _('Start new encounter'), _('Start a new encounter for the active patient right now.'))
588 self.Bind(wx.EVT_MENU, self.__on_start_new_encounter, item)
589
590
591
592
593
594 item = menu_emr.Append(-1, _('Statistics'), _('Show a high-level statistic summary of the EMR.'))
595 self.Bind(wx.EVT_MENU, self.__on_show_emr_summary, item)
596
597
598
599
600 menu_emr.AppendSeparator()
601
602
603 menu_emr_export = wx.Menu()
604
605 ID_EXPORT_EMR_ASCII = wx.NewId()
606 menu_emr_export.Append (
607 ID_EXPORT_EMR_ASCII,
608 _('Text document'),
609 _("Export the EMR of the active patient into a text file")
610 )
611 wx.EVT_MENU(self, ID_EXPORT_EMR_ASCII, self.OnExportEMR)
612
613 ID_EXPORT_EMR_JOURNAL = wx.NewId()
614 menu_emr_export.Append (
615 ID_EXPORT_EMR_JOURNAL,
616 _('Journal'),
617 _("Export the EMR of the active patient as a chronological journal into a text file")
618 )
619 wx.EVT_MENU(self, ID_EXPORT_EMR_JOURNAL, self.__on_export_emr_as_journal)
620
621 ID_EXPORT_MEDISTAR = wx.NewId()
622 menu_emr_export.Append (
623 ID_EXPORT_MEDISTAR,
624 _('MEDISTAR import format'),
625 _("GNUmed -> MEDISTAR. Export progress notes of active patient's active encounter into a text file.")
626 )
627 wx.EVT_MENU(self, ID_EXPORT_MEDISTAR, self.__on_export_for_medistar)
628
629 menu_emr.AppendMenu(wx.NewId(), _('Export as ...'), menu_emr_export)
630
631 menu_emr.AppendSeparator()
632
633 self.mainmenu.Append(menu_emr, _("&EMR"))
634 self.__gb['main.emrmenu'] = menu_emr
635
636
637 menu_paperwork = wx.Menu()
638
639 item = menu_paperwork.Append(-1, _('&Write letter'), _('Write a letter for the current patient.'))
640 self.Bind(wx.EVT_MENU, self.__on_new_letter, item)
641
642 self.mainmenu.Append(menu_paperwork, _('&Correspondence'))
643
644
645 self.menu_tools = wx.Menu()
646
647 ID_DICOM_VIEWER = wx.NewId()
648 viewer = _('no viewer installed')
649 if gmShellAPI.detect_external_binary(binary = 'ginkgocadx')[0]:
650 viewer = u'Ginkgo CADx'
651 elif os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
652 viewer = u'OsiriX'
653 elif gmShellAPI.detect_external_binary(binary = 'aeskulap')[0]:
654 viewer = u'Aeskulap'
655 elif gmShellAPI.detect_external_binary(binary = 'amide')[0]:
656 viewer = u'AMIDE'
657 elif gmShellAPI.detect_external_binary(binary = 'dicomscope')[0]:
658 viewer = u'DicomScope'
659 elif gmShellAPI.detect_external_binary(binary = 'xmedcon')[0]:
660 viewer = u'(x)medcon'
661 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)
662 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_dicom_viewer)
663 if viewer == _('no viewer installed'):
664 _log.info('neither of Ginkgo CADx / OsiriX / Aeskulap / AMIDE / DicomScope / xmedcon found, disabling "DICOM viewer" menu item')
665 self.menu_tools.Enable(id=ID_DICOM_VIEWER, enable=False)
666
667
668
669
670
671 ID = wx.NewId()
672 self.menu_tools.Append(ID, _('Snellen chart'), _('Display fullscreen snellen chart.'))
673 wx.EVT_MENU(self, ID, self.__on_snellen)
674
675 item = self.menu_tools.Append(-1, _('MI/stroke risk'), _('Acute coronary syndrome/stroke risk assessment.'))
676 self.Bind(wx.EVT_MENU, self.__on_acs_risk_assessment, item)
677
678 ID_DICOM_VIEWER = wx.NewId()
679 self.menu_tools.Append(ID_DICOM_VIEWER, u'arriba', _('arriba: cardiovascular risk assessment (%s).') % u'www.arriba-hausarzt.de')
680 wx.EVT_MENU(self, ID_DICOM_VIEWER, self.__on_arriba)
681 if not gmShellAPI.detect_external_binary(binary = 'arriba')[0]:
682 _log.info('<arriba> not found, disabling "arriba" menu item')
683 self.menu_tools.Enable(id = ID_DICOM_VIEWER, enable = False)
684
685
686
687 self.menu_tools.AppendSeparator()
688
689 self.mainmenu.Append(self.menu_tools, _("&Tools"))
690 self.__gb['main.toolsmenu'] = self.menu_tools
691
692
693 menu_knowledge = wx.Menu()
694
695
696 menu_drug_dbs = wx.Menu()
697
698 item = menu_drug_dbs.Append(-1, _('&Database'), _('Jump to the drug database configured as the default.'))
699 self.Bind(wx.EVT_MENU, self.__on_jump_to_drug_db, item)
700
701
702
703
704
705
706 menu_knowledge.AppendMenu(wx.NewId(), _('&Drug Resources'), menu_drug_dbs)
707
708 menu_id = wx.NewId()
709 menu_drug_dbs.Append(menu_id, u'kompendium.ch', _('Show "kompendium.ch" drug database (online, Switzerland)'))
710 wx.EVT_MENU(self, menu_id, self.__on_kompendium_ch)
711
712
713
714
715 ID_MEDICAL_LINKS = wx.NewId()
716 menu_knowledge.Append(ID_MEDICAL_LINKS, _('Medical links (www)'), _('Show a page of links to useful medical content.'))
717 wx.EVT_MENU(self, ID_MEDICAL_LINKS, self.__on_medical_links)
718
719 self.mainmenu.Append(menu_knowledge, _('&Knowledge'))
720 self.__gb['main.knowledgemenu'] = menu_knowledge
721
722
723 self.menu_office = wx.Menu()
724
725 item = self.menu_office.Append(-1, _('Audit trail'), _('Display database audit trail.'))
726 self.Bind(wx.EVT_MENU, self.__on_display_audit_trail, item)
727
728 self.menu_office.AppendSeparator()
729
730 self.mainmenu.Append(self.menu_office, _('&Office'))
731 self.__gb['main.officemenu'] = self.menu_office
732
733
734 help_menu = wx.Menu()
735
736 ID = wx.NewId()
737 help_menu.Append(ID, _('GNUmed wiki'), _('Go to the GNUmed wiki on the web.'))
738 wx.EVT_MENU(self, ID, self.__on_display_wiki)
739
740 ID = wx.NewId()
741 help_menu.Append(ID, _('User manual (www)'), _('Go to the User Manual on the web.'))
742 wx.EVT_MENU(self, ID, self.__on_display_user_manual_online)
743
744 item = help_menu.Append(-1, _('Menu reference (www)'), _('View the reference for menu items on the web.'))
745 self.Bind(wx.EVT_MENU, self.__on_menu_reference, item)
746
747 menu_debugging = wx.Menu()
748
749 ID_SCREENSHOT = wx.NewId()
750 menu_debugging.Append(ID_SCREENSHOT, _('Screenshot'), _('Save a screenshot of this GNUmed client.'))
751 wx.EVT_MENU(self, ID_SCREENSHOT, self.__on_save_screenshot)
752
753 item = menu_debugging.Append(-1, _('Show log file'), _('Show the log file in text viewer.'))
754 self.Bind(wx.EVT_MENU, self.__on_show_log_file, item)
755
756 ID = wx.NewId()
757 menu_debugging.Append(ID, _('Backup log file'), _('Backup the content of the log to another file.'))
758 wx.EVT_MENU(self, ID, self.__on_backup_log_file)
759
760 item = menu_debugging.Append(-1, _('Email log file'), _('Send the log file to the authors for help.'))
761 self.Bind(wx.EVT_MENU, self.__on_email_log_file, item)
762
763 ID = wx.NewId()
764 menu_debugging.Append(ID, _('Bug tracker'), _('Go to the GNUmed bug tracker on the web.'))
765 wx.EVT_MENU(self, ID, self.__on_display_bugtracker)
766
767 ID_UNBLOCK = wx.NewId()
768 menu_debugging.Append(ID_UNBLOCK, _('Unlock mouse'), _('Unlock mouse pointer in case it got stuck in hourglass mode.'))
769 wx.EVT_MENU(self, ID_UNBLOCK, self.__on_unblock_cursor)
770
771 item = menu_debugging.Append(-1, _('pgAdmin III'), _('pgAdmin III: Browse GNUmed database(s) in PostgreSQL server.'))
772 self.Bind(wx.EVT_MENU, self.__on_pgadmin3, item)
773
774
775
776
777 if _cfg.get(option = 'debug'):
778 ID_TOGGLE_PAT_LOCK = wx.NewId()
779 menu_debugging.Append(ID_TOGGLE_PAT_LOCK, _('Lock/unlock patient search'), _('Lock/unlock patient search - USE ONLY IF YOU KNOW WHAT YOU ARE DOING !'))
780 wx.EVT_MENU(self, ID_TOGGLE_PAT_LOCK, self.__on_toggle_patient_lock)
781
782 ID_TEST_EXCEPTION = wx.NewId()
783 menu_debugging.Append(ID_TEST_EXCEPTION, _('Test error handling'), _('Throw an exception to test error handling.'))
784 wx.EVT_MENU(self, ID_TEST_EXCEPTION, self.__on_test_exception)
785
786 ID = wx.NewId()
787 menu_debugging.Append(ID, _('Invoke inspector'), _('Invoke the widget hierarchy inspector (needs wxPython 2.8).'))
788 wx.EVT_MENU(self, ID, self.__on_invoke_inspector)
789 try:
790 import wx.lib.inspection
791 except ImportError:
792 menu_debugging.Enable(id = ID, enable = False)
793
794 help_menu.AppendMenu(wx.NewId(), _('Debugging ...'), menu_debugging)
795
796 help_menu.AppendSeparator()
797
798 help_menu.Append(wx.ID_ABOUT, _('About GNUmed'), "")
799 wx.EVT_MENU (self, wx.ID_ABOUT, self.OnAbout)
800
801 item = help_menu.Append(-1, _('About database'), _('Show information about the current database.'))
802 self.Bind(wx.EVT_MENU, self.__on_about_database, item)
803
804 item = help_menu.Append(-1, _('About contributors'), _('Show GNUmed contributors'))
805 self.Bind(wx.EVT_MENU, self.__on_show_contributors, item)
806
807 help_menu.AppendSeparator()
808
809 self.mainmenu.Append(help_menu, _("&Help"))
810
811 self.__gb['main.helpmenu'] = help_menu
812
813
814 self.SetMenuBar(self.mainmenu)
815
818
819
820
822 """register events we want to react to"""
823
824 wx.EVT_CLOSE(self, self.OnClose)
825 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
826 wx.EVT_END_SESSION(self, self._on_end_session)
827
828 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
829 gmDispatcher.connect(signal = u'name_mod_db', receiver = self._on_pat_name_changed)
830 gmDispatcher.connect(signal = u'identity_mod_db', receiver = self._on_pat_name_changed)
831 gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
832 gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
833 gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
834 gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
835 gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
836
837 gmPerson.gmCurrentPatient().register_pre_selection_callback(callback = self._pre_selection_callback)
838
839 - def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
840
841 _log.debug('registering plugin with menu system')
842 _log.debug(' generic name: %s', plugin_name)
843 _log.debug(' class name: %s', class_name)
844 _log.debug(' specific menu: %s', menu_name)
845 _log.debug(' menu item: %s', menu_item_name)
846
847
848 item = self.menu_plugins.Append(-1, plugin_name, _('Raise plugin [%s].') % plugin_name)
849 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
850 self.menu_id2plugin[item.Id] = class_name
851
852
853 if menu_name is not None:
854 menu = self.__gb['main.%smenu' % menu_name]
855 item = menu.Append(-1, menu_item_name, menu_help_string)
856 self.Bind(wx.EVT_MENU, self.__on_raise_a_plugin, item)
857 self.menu_id2plugin[item.Id] = class_name
858
859 return True
860
862 gmDispatcher.send (
863 signal = u'display_widget',
864 name = self.menu_id2plugin[evt.Id]
865 )
866
868 wx.Bell()
869 wx.Bell()
870 wx.Bell()
871 _log.warning('unhandled event detected: QUERY_END_SESSION')
872 _log.info('we should be saving ourselves from here')
873 gmLog2.flush()
874 print "unhandled event detected: QUERY_END_SESSION"
875
877 wx.Bell()
878 wx.Bell()
879 wx.Bell()
880 _log.warning('unhandled event detected: END_SESSION')
881 gmLog2.flush()
882 print "unhandled event detected: END_SESSION"
883
885 if not callable(callback):
886 raise TypeError(u'callback [%s] not callable' % callback)
887
888 self.__pre_exit_callbacks.append(callback)
889
890 - def _on_set_statustext_pubsub(self, context=None):
891 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), context.data['msg'])
892 wx.CallAfter(self.SetStatusText, msg)
893
894 try:
895 if context.data['beep']:
896 wx.Bell()
897 except KeyError:
898 pass
899
900 - def _on_set_statustext(self, msg=None, loglevel=None, beep=True):
901
902 if msg is None:
903 msg = _('programmer forgot to specify status message')
904
905 if loglevel is not None:
906 _log.log(loglevel, msg.replace('\015', ' ').replace('\012', ' '))
907
908 msg = u'%s %s' % (gmDateTime.pydt_now_here().strftime('%H:%M'), msg)
909 wx.CallAfter(self.SetStatusText, msg)
910
911 if beep:
912 wx.Bell()
913
915 wx.CallAfter(self.__on_db_maintenance_warning)
916
918
919 self.SetStatusText(_('The database will be shut down for maintenance in a few minutes.'))
920 wx.Bell()
921 if not wx.GetApp().IsActive():
922 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
923
924 gmHooks.run_hook_script(hook = u'db_maintenance_warning')
925
926 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
927 None,
928 -1,
929 caption = _('Database shutdown warning'),
930 question = _(
931 'The database will be shut down for maintenance\n'
932 'in a few minutes.\n'
933 '\n'
934 'In order to not suffer any loss of data you\n'
935 'will need to save your current work and log\n'
936 'out of this GNUmed client.\n'
937 ),
938 button_defs = [
939 {
940 u'label': _('Close now'),
941 u'tooltip': _('Close this GNUmed client immediately.'),
942 u'default': False
943 },
944 {
945 u'label': _('Finish work'),
946 u'tooltip': _('Finish and save current work first, then manually close this GNUmed client.'),
947 u'default': True
948 }
949 ]
950 )
951 decision = dlg.ShowModal()
952 if decision == wx.ID_YES:
953 top_win = wx.GetApp().GetTopWindow()
954 wx.CallAfter(top_win.Close)
955
957 wx.CallAfter(self.__on_request_user_attention, msg, urgent)
958
960
961 if not wx.GetApp().IsActive():
962 if urgent:
963 self.RequestUserAttention(flags = wx.USER_ATTENTION_ERROR)
964 else:
965 self.RequestUserAttention(flags = wx.USER_ATTENTION_INFO)
966
967 if msg is not None:
968 self.SetStatusText(msg)
969
970 if urgent:
971 wx.Bell()
972
973 gmHooks.run_hook_script(hook = u'request_user_attention')
974
976 wx.CallAfter(self.__on_pat_name_changed)
977
979 self.__update_window_title()
980
982 wx.CallAfter(self.__on_post_patient_selection, **kwargs)
983
985 self.__update_window_title()
986 try:
987 gmHooks.run_hook_script(hook = u'post_patient_activation')
988 except:
989 gmDispatcher.send(signal = 'statustext', msg = _('Cannot run script after patient activation.'))
990 raise
991
993 return self.__sanity_check_encounter()
994
1056
1057
1058
1061
1069
1070
1071
1086
1109
1111 from Gnumed.wxpython import gmAbout
1112 contribs = gmAbout.cContributorsDlg (
1113 parent = self,
1114 id = -1,
1115 title = _('GNUmed contributors'),
1116 size = wx.Size(400,600),
1117 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
1118 )
1119 contribs.ShowModal()
1120 del contribs
1121 del gmAbout
1122
1123
1124
1126 """Invoked from Menu GNUmed / Exit (which calls this ID_EXIT handler)."""
1127 _log.debug('gmTopLevelFrame._on_exit_gnumed() start')
1128 self.Close(True)
1129 _log.debug('gmTopLevelFrame._on_exit_gnumed() end')
1130
1133
1135 send = gmGuiHelpers.gm_show_question (
1136 _('This will send a notification about database downtime\n'
1137 'to all GNUmed clients connected to your database.\n'
1138 '\n'
1139 'Do you want to send the notification ?\n'
1140 ),
1141 _('Announcing database maintenance downtime')
1142 )
1143 if not send:
1144 return
1145 gmPG2.send_maintenance_notification()
1146
1147
1150
1151
1152
1165
1166 gmCfgWidgets.configure_string_option (
1167 message = _(
1168 'Some network installations cannot cope with loading\n'
1169 'documents of arbitrary size in one piece from the\n'
1170 'database (mainly observed on older Windows versions)\n.'
1171 '\n'
1172 'Under such circumstances documents need to be retrieved\n'
1173 'in chunks and reassembled on the client.\n'
1174 '\n'
1175 'Here you can set the size (in Bytes) above which\n'
1176 'GNUmed will retrieve documents in chunks. Setting this\n'
1177 'value to 0 will disable the chunking protocol.'
1178 ),
1179 option = 'horstspace.blob_export_chunk_size',
1180 bias = 'workplace',
1181 default_value = 1024 * 1024,
1182 validator = is_valid
1183 )
1184
1185
1186
1254
1258
1259
1260
1269
1270 gmCfgWidgets.configure_string_option (
1271 message = _(
1272 'When GNUmed cannot find an OpenOffice server it\n'
1273 'will try to start one. OpenOffice, however, needs\n'
1274 'some time to fully start up.\n'
1275 '\n'
1276 'Here you can set the time for GNUmed to wait for OOo.\n'
1277 ),
1278 option = 'external.ooo.startup_settle_time',
1279 bias = 'workplace',
1280 default_value = 2.0,
1281 validator = is_valid
1282 )
1283
1286
1301
1302 gmCfgWidgets.configure_string_option (
1303 message = _(
1304 'GNUmed will use this URL to access a website which lets\n'
1305 'you report an adverse drug reaction (ADR).\n'
1306 '\n'
1307 'If you leave this empty it will fall back\n'
1308 'to an URL for reporting ADRs in Germany.'
1309 ),
1310 option = 'external.urls.report_ADR',
1311 bias = 'user',
1312 default_value = german_default,
1313 validator = is_valid
1314 )
1315
1329
1330 gmCfgWidgets.configure_string_option (
1331 message = _(
1332 'GNUmed will use this URL to access a website which lets\n'
1333 'you report an adverse vaccination reaction (vADR).\n'
1334 '\n'
1335 'If you set it to a specific address that URL must be\n'
1336 'accessible now. If you leave it empty it will fall back\n'
1337 'to the URL for reporting other adverse drug reactions.'
1338 ),
1339 option = 'external.urls.report_vaccine_ADR',
1340 bias = 'user',
1341 default_value = german_default,
1342 validator = is_valid
1343 )
1344
1358
1359 gmCfgWidgets.configure_string_option (
1360 message = _(
1361 'GNUmed will use this URL to access an encyclopedia of\n'
1362 'measurement/lab methods from within the measurments grid.\n'
1363 '\n'
1364 'You can leave this empty but to set it to a specific\n'
1365 'address the URL must be accessible now.'
1366 ),
1367 option = 'external.urls.measurements_encyclopedia',
1368 bias = 'user',
1369 default_value = german_default,
1370 validator = is_valid
1371 )
1372
1386
1387 gmCfgWidgets.configure_string_option (
1388 message = _(
1389 'GNUmed will use this URL to access a page showing\n'
1390 'vaccination schedules.\n'
1391 '\n'
1392 'You can leave this empty but to set it to a specific\n'
1393 'address the URL must be accessible now.'
1394 ),
1395 option = 'external.urls.vaccination_plans',
1396 bias = 'user',
1397 default_value = german_default,
1398 validator = is_valid
1399 )
1400
1413
1414 gmCfgWidgets.configure_string_option (
1415 message = _(
1416 'Enter the shell command with which to start the\n'
1417 'the ACS risk assessment calculator.\n'
1418 '\n'
1419 'GNUmed will try to verify the path which may,\n'
1420 'however, fail if you are using an emulator such\n'
1421 'as Wine. Nevertheless, starting the calculator\n'
1422 'will work as long as the shell command is correct\n'
1423 'despite the failing test.'
1424 ),
1425 option = 'external.tools.acs_risk_calculator_cmd',
1426 bias = 'user',
1427 validator = is_valid
1428 )
1429
1432
1445
1446 gmCfgWidgets.configure_string_option (
1447 message = _(
1448 'Enter the shell command with which to start\n'
1449 'the FreeDiams drug database frontend.\n'
1450 '\n'
1451 'GNUmed will try to verify that path.'
1452 ),
1453 option = 'external.tools.freediams_cmd',
1454 bias = 'workplace',
1455 default_value = None,
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 IFAP drug database.\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 IFAP will work\n'
1480 'as long as the shell command is correct despite\n'
1481 'the failing test.'
1482 ),
1483 option = 'external.ifap-win.shell_command',
1484 bias = 'workplace',
1485 default_value = 'C:\Ifapwin\WIAMDB.EXE',
1486 validator = is_valid
1487 )
1488
1489
1490
1539
1540
1541
1558
1561
1564
1569
1570 gmCfgWidgets.configure_string_option (
1571 message = _(
1572 'When a patient is activated GNUmed checks the\n'
1573 "proximity of the patient's birthday.\n"
1574 '\n'
1575 'If the birthday falls within the range of\n'
1576 ' "today %s <the interval you set here>"\n'
1577 'GNUmed will remind you of the recent or\n'
1578 'imminent anniversary.'
1579 ) % u'\u2213',
1580 option = u'patient_search.dob_warn_interval',
1581 bias = 'user',
1582 default_value = '1 week',
1583 validator = is_valid
1584 )
1585
1587
1588 gmCfgWidgets.configure_boolean_option (
1589 parent = self,
1590 question = _(
1591 'When adding progress notes do you want to\n'
1592 'allow opening several unassociated, new\n'
1593 'episodes for a patient at once ?\n'
1594 '\n'
1595 'This can be particularly helpful when entering\n'
1596 'progress notes on entirely new patients presenting\n'
1597 'with a multitude of problems on their first visit.'
1598 ),
1599 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1600 button_tooltips = [
1601 _('Yes, allow for multiple new episodes concurrently.'),
1602 _('No, only allow editing one new episode at a time.')
1603 ]
1604 )
1605
1607
1608 gmCfgWidgets.configure_boolean_option (
1609 parent = self,
1610 question = _(
1611 'When activating a patient, do you want GNUmed to\n'
1612 'auto-open editors for all active problems that were\n'
1613 'touched upon during the current and the most recent\n'
1614 'encounter ?'
1615 ),
1616 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1617 button_tooltips = [
1618 _('Yes, auto-open editors for all problems of the most recent encounter.'),
1619 _('No, only auto-open one editor for a new, unassociated problem.')
1620 ]
1621 )
1622
1668
1669
1670
1673
1676
1690
1692 gmCfgWidgets.configure_boolean_option (
1693 parent = self,
1694 question = _(
1695 'Do you want GNUmed to show the encounter\n'
1696 'details editor when changing the active patient ?'
1697 ),
1698 option = 'encounter.show_editor_before_patient_change',
1699 button_tooltips = [
1700 _('Yes, show the encounter editor if it seems appropriate.'),
1701 _('No, never show the encounter editor even if it would seem useful.')
1702 ]
1703 )
1704
1709
1710 gmCfgWidgets.configure_string_option (
1711 message = _(
1712 'When a patient is activated GNUmed checks the\n'
1713 'chart for encounters lacking any entries.\n'
1714 '\n'
1715 'Any such encounters older than what you set\n'
1716 'here will be removed from the medical record.\n'
1717 '\n'
1718 'To effectively disable removal of such encounters\n'
1719 'set this option to an improbable value.\n'
1720 ),
1721 option = 'encounter.ttl_if_empty',
1722 bias = 'user',
1723 default_value = '1 week',
1724 validator = is_valid
1725 )
1726
1731
1732 gmCfgWidgets.configure_string_option (
1733 message = _(
1734 'When a patient is activated GNUmed checks the\n'
1735 'age of the most recent encounter.\n'
1736 '\n'
1737 'If that encounter is younger than this age\n'
1738 'the existing encounter will be continued.\n'
1739 '\n'
1740 '(If it is really old a new encounter is\n'
1741 ' started, or else GNUmed will ask you.)\n'
1742 ),
1743 option = 'encounter.minimum_ttl',
1744 bias = 'user',
1745 default_value = '1 hour 30 minutes',
1746 validator = is_valid
1747 )
1748
1753
1754 gmCfgWidgets.configure_string_option (
1755 message = _(
1756 'When a patient is activated GNUmed checks the\n'
1757 'age of the most recent encounter.\n'
1758 '\n'
1759 'If that encounter is older than this age\n'
1760 'GNUmed will always start a new encounter.\n'
1761 '\n'
1762 '(If it is very recent the existing encounter\n'
1763 ' is continued, or else GNUmed will ask you.)\n'
1764 ),
1765 option = 'encounter.maximum_ttl',
1766 bias = 'user',
1767 default_value = '6 hours',
1768 validator = is_valid
1769 )
1770
1779
1780 gmCfgWidgets.configure_string_option (
1781 message = _(
1782 'At any time there can only be one open (ongoing)\n'
1783 'episode for each health issue.\n'
1784 '\n'
1785 'When you try to open (add data to) an episode on a health\n'
1786 'issue GNUmed will check for an existing open episode on\n'
1787 'that issue. If there is any it will check the age of that\n'
1788 'episode. The episode is closed if it has been dormant (no\n'
1789 'data added, that is) for the period of time (in days) you\n'
1790 'set here.\n'
1791 '\n'
1792 "If the existing episode hasn't been dormant long enough\n"
1793 'GNUmed will consult you what to do.\n'
1794 '\n'
1795 'Enter maximum episode dormancy in DAYS:'
1796 ),
1797 option = 'episode.ttl',
1798 bias = 'user',
1799 default_value = 60,
1800 validator = is_valid
1801 )
1802
1833
1848
1873
1885
1886 gmCfgWidgets.configure_string_option (
1887 message = _(
1888 'GNUmed can check for new releases being available. To do\n'
1889 'so it needs to load version information from an URL.\n'
1890 '\n'
1891 'The default URL is:\n'
1892 '\n'
1893 ' http://www.gnumed.de/downloads/gnumed-versions.txt\n'
1894 '\n'
1895 'but you can configure any other URL locally. Note\n'
1896 'that you must enter the location as a valid URL.\n'
1897 'Depending on the URL the client will need online\n'
1898 'access when checking for updates.'
1899 ),
1900 option = u'horstspace.update.url',
1901 bias = u'workplace',
1902 default_value = u'http://www.gnumed.de/downloads/gnumed-versions.txt',
1903 validator = is_valid
1904 )
1905
1923
1940
1957
1968
1969 gmCfgWidgets.configure_string_option (
1970 message = _(
1971 'GNUmed can show the document review dialog after\n'
1972 'calling the appropriate viewer for that document.\n'
1973 '\n'
1974 'Select the conditions under which you want\n'
1975 'GNUmed to do so:\n'
1976 '\n'
1977 ' 0: never display the review dialog\n'
1978 ' 1: always display the dialog\n'
1979 ' 2: only if there is no previous review by me\n'
1980 ' 3: only if there is no previous review at all\n'
1981 ' 4: only if there is no review by the responsible reviewer\n'
1982 '\n'
1983 'Note that if a viewer is configured to not block\n'
1984 'GNUmed during document display the review dialog\n'
1985 'will actually appear in parallel to the viewer.'
1986 ),
1987 option = u'horstspace.document_viewer.review_after_display',
1988 bias = u'user',
1989 default_value = 3,
1990 validator = is_valid
1991 )
1992
1994
1995
1996 master_data_lists = [
1997 'adr',
1998 'billables',
1999 'drugs',
2000 'codes',
2001 'communication_channel_types',
2002 'substances_in_brands',
2003 'substances',
2004 'labs',
2005 'form_templates',
2006 'doc_types',
2007 'enc_types',
2008 'text_expansions',
2009 'meta_test_types',
2010 'orgs',
2011 'patient_tags',
2012 'provinces',
2013 'db_translations',
2014 'ref_data_sources',
2015 'test_types',
2016 'vacc_indications',
2017 'vaccines',
2018 'workplaces'
2019 ]
2020
2021 master_data_list_names = {
2022 'adr': _('Addresses (likely slow)'),
2023 'drugs': _('Branded drugs (as marketed)'),
2024 'codes': _('Codes and their respective terms'),
2025 'communication_channel_types': _('Communication channel types'),
2026 'substances_in_brands': _('Components of branded drugs (substances in brands)'),
2027 'labs': _('Diagnostic organizations (path labs, ...)'),
2028 'form_templates': _('Document templates (forms, letters, plots, ...)'),
2029 'doc_types': _('Document types'),
2030 'enc_types': _('Encounter types'),
2031 'text_expansions': _('Keyword based text expansion macros'),
2032 'meta_test_types': _('Meta test/measurement types'),
2033 'orgs': _('Organizations with their units, addresses, and comm channels'),
2034 'patient_tags': _('Patient tags'),
2035 'provinces': _('Provinces (counties, territories, states, regions, ...)'),
2036 'db_translations': _('String translations in the database'),
2037 'test_types': _('Test/measurement types'),
2038 'vacc_indications': _('Vaccination targets (conditions known to be preventable by vaccination)'),
2039 'vaccines': _('Vaccines'),
2040 'workplaces': _('Workplace profiles (which plugins to load)'),
2041 'substances': _('Consumable substances'),
2042 'billables': _('Billable items'),
2043 'ref_data_sources': _('Reference data sources')
2044 }
2045
2046 map_list2handler = {
2047 'form_templates': gmFormWidgets.manage_form_templates,
2048 'doc_types': gmDocumentWidgets.manage_document_types,
2049 'text_expansions': gmProviderInboxWidgets.configure_keyword_text_expansion,
2050 'db_translations': gmI18nWidgets.manage_translations,
2051 'codes': gmCodingWidgets.browse_coded_terms,
2052 'enc_types': gmEMRStructWidgets.manage_encounter_types,
2053 'provinces': gmAddressWidgets.manage_provinces,
2054 'workplaces': gmProviderInboxWidgets.configure_workplace_plugins,
2055 'drugs': gmMedicationWidgets.manage_branded_drugs,
2056 'substances_in_brands': gmMedicationWidgets.manage_drug_components,
2057 'labs': gmMeasurementWidgets.manage_measurement_orgs,
2058 'test_types': gmMeasurementWidgets.manage_measurement_types,
2059 'meta_test_types': gmMeasurementWidgets.manage_meta_test_types,
2060 'vaccines': gmVaccWidgets.manage_vaccines,
2061 'vacc_indications': gmVaccWidgets.manage_vaccination_indications,
2062 'orgs': gmOrganizationWidgets.manage_orgs,
2063 'adr': gmAddressWidgets.manage_addresses,
2064 'substances': gmMedicationWidgets.manage_consumable_substances,
2065 'patient_tags': gmDemographicsWidgets.manage_tag_images,
2066 'communication_channel_types': gmContactWidgets.manage_comm_channel_types,
2067 'billables': gmBillingWidgets.manage_billables,
2068 'ref_data_sources': gmCodingWidgets.browse_data_sources
2069 }
2070
2071
2072 def edit(item):
2073 try: map_list2handler[item](parent = self)
2074 except KeyError: pass
2075 return False
2076
2077
2078 gmListWidgets.get_choices_from_list (
2079 parent = self,
2080 caption = _('Master data management'),
2081 choices = [ master_data_list_names[lst] for lst in master_data_lists],
2082 data = master_data_lists,
2083 columns = [_('Select the list you want to manage:')],
2084 edit_callback = edit,
2085 single_selection = True,
2086 ignore_OK_button = True
2087 )
2088
2090
2091 found, cmd = gmShellAPI.detect_external_binary(binary = 'ginkgocadx')
2092 if found:
2093 gmShellAPI.run_command_in_shell(cmd, blocking=False)
2094 return
2095
2096 if os.access('/Applications/OsiriX.app/Contents/MacOS/OsiriX', os.X_OK):
2097 gmShellAPI.run_command_in_shell('/Applications/OsiriX.app/Contents/MacOS/OsiriX', blocking = False)
2098 return
2099
2100 for viewer in ['aeskulap', 'amide', 'dicomscope', 'xmedcon']:
2101 found, cmd = gmShellAPI.detect_external_binary(binary = viewer)
2102 if found:
2103 gmShellAPI.run_command_in_shell(cmd, blocking = False)
2104 return
2105
2106 gmDispatcher.send(signal = 'statustext', msg = _('No DICOM viewer found.'), beep = True)
2107
2109
2110 curr_pat = gmPerson.gmCurrentPatient()
2111
2112 arriba = gmArriba.cArriba()
2113 pat = gmTools.bool2subst(curr_pat.connected, curr_pat, None)
2114 if not arriba.run(patient = pat, debug = _cfg.get(option = 'debug')):
2115 return
2116
2117
2118 if curr_pat is None:
2119 return
2120
2121 if arriba.pdf_result is None:
2122 return
2123
2124 doc = gmDocumentWidgets.save_file_as_new_document (
2125 parent = self,
2126 filename = arriba.pdf_result,
2127 document_type = _('risk assessment')
2128 )
2129
2130 try: os.remove(arriba.pdf_result)
2131 except StandardError: _log.exception('cannot remove [%s]', arriba.pdf_result)
2132
2133 if doc is None:
2134 return
2135
2136 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2137 doc.save()
2138
2139 try:
2140 open(arriba.xml_result).close()
2141 part = doc.add_part(file = arriba.xml_result)
2142 except StandardError:
2143 _log.exception('error accessing [%s]', arriba.xml_result)
2144 gmDispatcher.send(signal = u'statustext', msg = _('[arriba] XML result not found in [%s]') % arriba.xml_result, beep = False)
2145
2146 if part is None:
2147 return
2148
2149 part['obj_comment'] = u'XML-Daten'
2150 part['filename'] = u'arriba-result.xml'
2151 part.save()
2152
2154
2155 dbcfg = gmCfg.cCfgSQL()
2156 cmd = dbcfg.get2 (
2157 option = u'external.tools.acs_risk_calculator_cmd',
2158 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2159 bias = 'user'
2160 )
2161
2162 if cmd is None:
2163 gmDispatcher.send(signal = u'statustext', msg = _('ACS risk assessment calculator not configured.'), beep = True)
2164 return
2165
2166 cwd = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
2167 try:
2168 subprocess.check_call (
2169 args = (cmd,),
2170 close_fds = True,
2171 cwd = cwd
2172 )
2173 except (OSError, ValueError, subprocess.CalledProcessError):
2174 _log.exception('there was a problem executing [%s]', cmd)
2175 gmDispatcher.send(signal = u'statustext', msg = _('Cannot run [%s] !') % cmd, beep = True)
2176 return
2177
2178 pdfs = glob.glob(os.path.join(cwd, 'arriba-%s-*.pdf' % gmDateTime.pydt_now_here().strftime('%Y-%m-%d')))
2179 for pdf in pdfs:
2180 try:
2181 open(pdf).close()
2182 except:
2183 _log.exception('error accessing [%s]', pdf)
2184 gmDispatcher.send(signal = u'statustext', msg = _('There was a problem accessing the [arriba] result in [%s] !') % pdf, beep = True)
2185 continue
2186
2187 doc = gmDocumentWidgets.save_file_as_new_document (
2188 parent = self,
2189 filename = pdf,
2190 document_type = u'risk assessment'
2191 )
2192
2193 try:
2194 os.remove(pdf)
2195 except StandardError:
2196 _log.exception('cannot remove [%s]', pdf)
2197
2198 if doc is None:
2199 continue
2200 doc['comment'] = u'arriba: %s' % _('cardiovascular risk assessment')
2201 doc.save()
2202
2203 return
2204
2206 dlg = gmSnellen.cSnellenCfgDlg()
2207 if dlg.ShowModal() != wx.ID_OK:
2208 return
2209
2210 frame = gmSnellen.cSnellenChart (
2211 width = dlg.vals[0],
2212 height = dlg.vals[1],
2213 alpha = dlg.vals[2],
2214 mirr = dlg.vals[3],
2215 parent = None
2216 )
2217 frame.CentreOnScreen(wx.BOTH)
2218
2219
2220 frame.Show(True)
2221
2222
2225
2228
2231
2232
2233
2237
2238
2239
2241 wx.CallAfter(self.__save_screenshot)
2242 evt.Skip()
2243
2245
2246 time.sleep(0.5)
2247
2248 rect = self.GetRect()
2249
2250
2251 if sys.platform == 'linux2':
2252 client_x, client_y = self.ClientToScreen((0, 0))
2253 border_width = client_x - rect.x
2254 title_bar_height = client_y - rect.y
2255
2256 if self.GetMenuBar():
2257 title_bar_height /= 2
2258 rect.width += (border_width * 2)
2259 rect.height += title_bar_height + border_width
2260
2261 wdc = wx.ScreenDC()
2262 mdc = wx.MemoryDC()
2263 img = wx.EmptyBitmap(rect.width, rect.height)
2264 mdc.SelectObject(img)
2265 mdc.Blit (
2266 0, 0,
2267 rect.width, rect.height,
2268 wdc,
2269 rect.x, rect.y
2270 )
2271
2272
2273 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'gnumed-screenshot-%s.png')) % pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
2274 img.SaveFile(fname, wx.BITMAP_TYPE_PNG)
2275 gmDispatcher.send(signal = 'statustext', msg = _('Saved screenshot to file [%s].') % fname)
2276
2278
2279 raise ValueError('raised ValueError to test exception handling')
2280
2282 import wx.lib.inspection
2283 wx.lib.inspection.InspectionTool().Show()
2284
2287
2290
2293
2296
2303
2307
2310
2317
2322
2324 name = os.path.basename(gmLog2._logfile_name)
2325 name, ext = os.path.splitext(name)
2326 new_name = '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
2327 new_path = os.path.expanduser(os.path.join('~', 'gnumed', 'logs'))
2328
2329 dlg = wx.FileDialog (
2330 parent = self,
2331 message = _("Save current log as..."),
2332 defaultDir = new_path,
2333 defaultFile = new_name,
2334 wildcard = "%s (*.log)|*.log" % _("log files"),
2335 style = wx.SAVE
2336 )
2337 choice = dlg.ShowModal()
2338 new_name = dlg.GetPath()
2339 dlg.Destroy()
2340 if choice != wx.ID_OK:
2341 return True
2342
2343 _log.warning('syncing log file for backup to [%s]', new_name)
2344 gmLog2.flush()
2345 shutil.copy2(gmLog2._logfile_name, new_name)
2346 gmDispatcher.send('statustext', msg = _('Log file backed up as [%s].') % new_name)
2347
2350
2351
2352
2354 """This is the wx.EVT_CLOSE handler.
2355
2356 - framework still functional
2357 """
2358 _log.debug('gmTopLevelFrame.OnClose() start')
2359 self._clean_exit()
2360 self.Destroy()
2361 _log.debug('gmTopLevelFrame.OnClose() end')
2362 return True
2363
2369
2374
2382
2389
2396
2403
2413
2421
2429
2437
2445
2454
2463
2471
2488
2491
2494
2496
2497 pat = gmPerson.gmCurrentPatient()
2498 if not pat.connected:
2499 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR journal. No active patient.'))
2500 return False
2501
2502 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
2503
2504 aDefDir = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', pat['dirname']))
2505 gmTools.mkdir(aDefDir)
2506
2507 fname = '%s-%s_%s.txt' % (_('emr-journal'), pat['lastnames'], pat['firstnames'])
2508 dlg = wx.FileDialog (
2509 parent = self,
2510 message = _("Save patient's EMR journal as..."),
2511 defaultDir = aDefDir,
2512 defaultFile = fname,
2513 wildcard = aWildcard,
2514 style = wx.SAVE
2515 )
2516 choice = dlg.ShowModal()
2517 fname = dlg.GetPath()
2518 dlg.Destroy()
2519 if choice != wx.ID_OK:
2520 return True
2521
2522 _log.debug('exporting EMR journal to [%s]' % fname)
2523
2524 exporter = gmPatientExporter.cEMRJournalExporter()
2525
2526 wx.BeginBusyCursor()
2527 try:
2528 fname = exporter.export_to_file(filename = fname)
2529 except:
2530 wx.EndBusyCursor()
2531 gmGuiHelpers.gm_show_error (
2532 _('Error exporting patient EMR as chronological journal.'),
2533 _('EMR journal export')
2534 )
2535 raise
2536 wx.EndBusyCursor()
2537
2538 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported EMR as chronological journal into file [%s].') % fname, beep=False)
2539
2540 return True
2541
2548
2550 curr_pat = gmPerson.gmCurrentPatient()
2551 if not curr_pat.connected:
2552 gmDispatcher.send(signal = 'statustext', msg = _('Cannot add tag to person. No active patient.'))
2553 return
2554
2555 tag = gmDemographicsWidgets.manage_tag_images(parent = self)
2556 if tag is None:
2557 return
2558
2559 tag = curr_pat.add_tag(tag['pk_tag_image'])
2560 msg = _('Edit the comment on tag [%s]') % tag['l10n_description']
2561 comment = wx.GetTextFromUser (
2562 message = msg,
2563 caption = _('Editing tag comment'),
2564 default_value = gmTools.coalesce(tag['comment'], u''),
2565 parent = self
2566 )
2567
2568 if comment == u'':
2569 return
2570
2571 if comment.strip() == tag['comment']:
2572 return
2573
2574 if comment == u' ':
2575 tag['comment'] = None
2576 else:
2577 tag['comment'] = comment.strip()
2578
2579 tag.save()
2580
2590
2592 curr_pat = gmPerson.gmCurrentPatient()
2593 if not curr_pat.connected:
2594 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export patient as GDT. No active patient.'))
2595 return False
2596
2597 enc = 'cp850'
2598 fname = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT', 'current-patient.gdt'))
2599 curr_pat.export_as_gdt(filename = fname, encoding = enc)
2600 gmDispatcher.send(signal = 'statustext', msg = _('Exported demographics to GDT file [%s].') % fname)
2601
2604
2612
2620
2623
2630
2634
2637
2638
2639
2640
2643
2646
2651
2653 """Cleanup helper.
2654
2655 - should ALWAYS be called when this program is
2656 to be terminated
2657 - ANY code that should be executed before a
2658 regular shutdown should go in here
2659 - framework still functional
2660 """
2661 _log.debug('gmTopLevelFrame._clean_exit() start')
2662
2663
2664 listener = gmBackendListener.gmBackendListener()
2665 try:
2666 listener.shutdown()
2667 except:
2668 _log.exception('cannot stop backend notifications listener thread')
2669
2670
2671 if _scripting_listener is not None:
2672 try:
2673 _scripting_listener.shutdown()
2674 except:
2675 _log.exception('cannot stop scripting listener thread')
2676
2677
2678 self.clock_update_timer.Stop()
2679 gmTimer.shutdown()
2680 gmPhraseWheel.shutdown()
2681
2682
2683 for call_back in self.__pre_exit_callbacks:
2684 try:
2685 call_back()
2686 except:
2687 print "*** pre-exit callback failed ***"
2688 print call_back
2689 _log.exception('callback [%s] failed', call_back)
2690
2691
2692 gmDispatcher.send(u'application_closing')
2693
2694
2695 gmDispatcher.disconnect(self._on_set_statustext, 'statustext')
2696
2697
2698 curr_width, curr_height = self.GetClientSizeTuple()
2699 _log.info('GUI size at shutdown: [%s:%s]' % (curr_width, curr_height))
2700 dbcfg = gmCfg.cCfgSQL()
2701 dbcfg.set (
2702 option = 'main.window.width',
2703 value = curr_width,
2704 workplace = gmSurgery.gmCurrentPractice().active_workplace
2705 )
2706 dbcfg.set (
2707 option = 'main.window.height',
2708 value = curr_height,
2709 workplace = gmSurgery.gmCurrentPractice().active_workplace
2710 )
2711
2712 if _cfg.get(option = 'debug'):
2713 print '---=== GNUmed shutdown ===---'
2714 try:
2715 print _('You have to manually close this window to finalize shutting down GNUmed.')
2716 print _('This is so that you can inspect the console output at your leisure.')
2717 except UnicodeEncodeError:
2718 print 'You have to manually close this window to finalize shutting down GNUmed.'
2719 print 'This is so that you can inspect the console output at your leisure.'
2720 print '---=== GNUmed shutdown ===---'
2721
2722
2723 gmExceptionHandlingWidgets.uninstall_wx_exception_handler()
2724
2725
2726 import threading
2727 _log.debug("%s active threads", threading.activeCount())
2728 for t in threading.enumerate():
2729 _log.debug('thread %s', t)
2730
2731 _log.debug('gmTopLevelFrame._clean_exit() end')
2732
2733
2734
2736
2737 if _cfg.get(option = 'slave'):
2738 self.__title_template = u'GMdS: %%(pat)s [%%(prov)s@%%(wp)s] (%s:%s)' % (
2739 _cfg.get(option = 'slave personality'),
2740 _cfg.get(option = 'xml-rpc port')
2741 )
2742 else:
2743 self.__title_template = u'GMd: %(pat)s [%(prov)s@%(wp)s]'
2744
2746 """Update title of main window based on template.
2747
2748 This gives nice tooltips on iconified GNUmed instances.
2749
2750 User research indicates that in the title bar people want
2751 the date of birth, not the age, so please stick to this
2752 convention.
2753 """
2754 args = {}
2755
2756 pat = gmPerson.gmCurrentPatient()
2757 if pat.connected:
2758 args['pat'] = u'%s %s %s (%s) #%d' % (
2759 gmTools.coalesce(pat['title'], u'', u'%.4s'),
2760 pat['firstnames'],
2761 pat['lastnames'],
2762 pat.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()),
2763 pat['pk_identity']
2764 )
2765 else:
2766 args['pat'] = _('no patient')
2767
2768 args['prov'] = u'%s%s.%s' % (
2769 gmTools.coalesce(_provider['title'], u'', u'%s '),
2770 _provider['firstnames'][:1],
2771 _provider['lastnames']
2772 )
2773
2774 args['wp'] = gmSurgery.gmCurrentPractice().active_workplace
2775
2776 self.SetTitle(self.__title_template % args)
2777
2778
2780 sb = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
2781 sb.SetStatusWidths([-1, 225])
2782
2783 self.clock_update_timer = wx.PyTimer(self._cb_update_clock)
2784 self._cb_update_clock()
2785
2786 self.clock_update_timer.Start(milliseconds = 1000)
2787
2789 """Displays date and local time in the second slot of the status bar"""
2790 t = time.localtime(time.time())
2791 st = time.strftime('%c', t).decode(gmI18N.get_encoding(), 'replace')
2792 self.SetStatusText(st, 1)
2793
2795 """Lock GNUmed client against unauthorized access"""
2796
2797
2798
2799 return
2800
2802 """Unlock the main notebook widgets
2803 As long as we are not logged into the database backend,
2804 all pages but the 'login' page of the main notebook widget
2805 are locked; i.e. not accessible by the user
2806 """
2807
2808
2809
2810
2811
2812 return
2813
2815 wx.LayoutAlgorithm().LayoutWindow (self.LayoutMgr, self.nb)
2816
2818
2820
2821 self.__starting_up = True
2822
2823 gmExceptionHandlingWidgets.install_wx_exception_handler()
2824 gmExceptionHandlingWidgets.set_client_version(_cfg.get(option = 'client_version'))
2825
2826
2827
2828
2829 self.SetAppName(u'gnumed')
2830 self.SetVendorName(u'The GNUmed Development Community.')
2831 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
2832 paths.init_paths(wx = wx, app_name = u'gnumed')
2833
2834 if not self.__setup_prefs_file():
2835 return False
2836
2837 gmExceptionHandlingWidgets.set_sender_email(gmSurgery.gmCurrentPractice().user_email)
2838
2839 self.__guibroker = gmGuiBroker.GuiBroker()
2840 self.__setup_platform()
2841
2842 if not self.__establish_backend_connection():
2843 return False
2844
2845 if not _cfg.get(option = 'skip-update-check'):
2846 self.__check_for_updates()
2847
2848 if _cfg.get(option = 'slave'):
2849 if not self.__setup_scripting_listener():
2850 return False
2851
2852
2853 frame = gmTopLevelFrame(None, -1, _('GNUmed client'), (640, 440))
2854 frame.CentreOnScreen(wx.BOTH)
2855 self.SetTopWindow(frame)
2856 frame.Show(True)
2857
2858 if _cfg.get(option = 'debug'):
2859 self.RedirectStdio()
2860 self.SetOutputWindowAttributes(title = _('GNUmed stdout/stderr window'))
2861
2862
2863 print '---=== GNUmed startup ===---'
2864 print _('redirecting STDOUT/STDERR to this log window')
2865 print '---=== GNUmed startup ===---'
2866
2867 self.__setup_user_activity_timer()
2868 self.__register_events()
2869
2870 wx.CallAfter(self._do_after_init)
2871
2872 return True
2873
2875 """Called internally by wxPython after EVT_CLOSE has been handled on last frame.
2876
2877 - after destroying all application windows and controls
2878 - before wx.Windows internal cleanup
2879 """
2880 _log.debug('gmApp.OnExit() start')
2881
2882 self.__shutdown_user_activity_timer()
2883
2884 if _cfg.get(option = 'debug'):
2885 self.RestoreStdio()
2886 sys.stdin = sys.__stdin__
2887 sys.stdout = sys.__stdout__
2888 sys.stderr = sys.__stderr__
2889
2890 _log.debug('gmApp.OnExit() end')
2891
2893 wx.Bell()
2894 wx.Bell()
2895 wx.Bell()
2896 _log.warning('unhandled event detected: QUERY_END_SESSION')
2897 _log.info('we should be saving ourselves from here')
2898 gmLog2.flush()
2899 print "unhandled event detected: QUERY_END_SESSION"
2900
2902 wx.Bell()
2903 wx.Bell()
2904 wx.Bell()
2905 _log.warning('unhandled event detected: END_SESSION')
2906 gmLog2.flush()
2907 print "unhandled event detected: END_SESSION"
2908
2919
2921 self.user_activity_detected = True
2922 evt.Skip()
2923
2925
2926 if self.user_activity_detected:
2927 self.elapsed_inactivity_slices = 0
2928 self.user_activity_detected = False
2929 self.elapsed_inactivity_slices += 1
2930 else:
2931 if self.elapsed_inactivity_slices >= self.max_user_inactivity_slices:
2932
2933 pass
2934
2935 self.user_activity_timer.Start(oneShot = True)
2936
2937
2938
2940 try:
2941 kwargs['originated_in_database']
2942 print '==> got notification from database "%s":' % kwargs['signal']
2943 except KeyError:
2944 print '==> received signal from client: "%s"' % kwargs['signal']
2945
2946 del kwargs['signal']
2947 for key in kwargs.keys():
2948 print ' [%s]: %s' % (key, kwargs[key])
2949
2955
2957 self.user_activity_detected = True
2958 self.elapsed_inactivity_slices = 0
2959
2960 self.max_user_inactivity_slices = 15
2961 self.user_activity_timer = gmTimer.cTimer (
2962 callback = self._on_user_activity_timer_expired,
2963 delay = 2000
2964 )
2965 self.user_activity_timer.Start(oneShot=True)
2966
2968 try:
2969 self.user_activity_timer.Stop()
2970 del self.user_activity_timer
2971 except:
2972 pass
2973
2975 wx.EVT_QUERY_END_SESSION(self, self._on_query_end_session)
2976 wx.EVT_END_SESSION(self, self._on_end_session)
2977
2978
2979
2980
2981
2982 self.Bind(wx.EVT_ACTIVATE_APP, self._on_app_activated)
2983
2984 self.Bind(wx.EVT_MOUSE_EVENTS, self._on_user_activity)
2985 self.Bind(wx.EVT_KEY_DOWN, self._on_user_activity)
2986
2987 if _cfg.get(option = 'debug'):
2988 gmDispatcher.connect(receiver = self._signal_debugging_monitor)
2989 _log.debug('connected signal monitor')
2990
3006
3008 """Handle all the database related tasks necessary for startup."""
3009
3010
3011 override = _cfg.get(option = '--override-schema-check', source_order = [('cli', 'return')])
3012
3013 from Gnumed.wxpython import gmAuthWidgets
3014 connected = gmAuthWidgets.connect_to_database (
3015 expected_version = gmPG2.map_client_branch2required_db_version[_cfg.get(option = 'client_branch')],
3016 require_version = not override
3017 )
3018 if not connected:
3019 _log.warning("Login attempt unsuccessful. Can't run GNUmed without database connection")
3020 return False
3021
3022
3023 try:
3024 global _provider
3025 _provider = gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
3026 except ValueError:
3027 account = gmPG2.get_current_user()
3028 _log.exception('DB account [%s] cannot be used as a GNUmed staff login', account)
3029 msg = _(
3030 'The database account [%s] cannot be used as a\n'
3031 'staff member login for GNUmed. There was an\n'
3032 'error retrieving staff details for it.\n\n'
3033 'Please ask your administrator for help.\n'
3034 ) % account
3035 gmGuiHelpers.gm_show_error(msg, _('Checking access permissions'))
3036 return False
3037
3038
3039 tmp = '%s%s %s (%s = %s)' % (
3040 gmTools.coalesce(_provider['title'], ''),
3041 _provider['firstnames'],
3042 _provider['lastnames'],
3043 _provider['short_alias'],
3044 _provider['db_user']
3045 )
3046 gmExceptionHandlingWidgets.set_staff_name(staff_name = tmp)
3047
3048
3049 surgery = gmSurgery.gmCurrentPractice()
3050 msg = surgery.db_logon_banner
3051 if msg.strip() != u'':
3052
3053 login = gmPG2.get_default_login()
3054 auth = u'\n%s\n\n' % (_('Database <%s> on <%s>') % (
3055 login.database,
3056 gmTools.coalesce(login.host, u'localhost')
3057 ))
3058 msg = auth + msg + u'\n\n'
3059
3060 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3061 None,
3062
3063 -1,
3064 caption = _('Verifying database'),
3065 question = gmTools.wrap(msg, 60, initial_indent = u' ', subsequent_indent = u' '),
3066 button_defs = [
3067 {'label': _('Connect'), 'tooltip': _('Yes, connect to this database.'), 'default': True},
3068 {'label': _('Disconnect'), 'tooltip': _('No, do not connect to this database.'), 'default': False}
3069 ]
3070 )
3071 go_on = dlg.ShowModal()
3072 dlg.Destroy()
3073 if go_on != wx.ID_YES:
3074 _log.info('user decided to not connect to this database')
3075 return False
3076
3077
3078 self.__check_db_lang()
3079
3080 return True
3081
3083 """Setup access to a config file for storing preferences."""
3084
3085 paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
3086
3087 candidates = []
3088 explicit_file = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
3089 if explicit_file is not None:
3090 candidates.append(explicit_file)
3091
3092 candidates.append(os.path.join(paths.user_config_dir, 'gnumed.conf'))
3093 candidates.append(os.path.join(paths.local_base_dir, 'gnumed.conf'))
3094 candidates.append(os.path.join(paths.working_dir, 'gnumed.conf'))
3095
3096 prefs_file = None
3097 for candidate in candidates:
3098 try:
3099 open(candidate, 'a+').close()
3100 prefs_file = candidate
3101 break
3102 except IOError:
3103 continue
3104
3105 if prefs_file is None:
3106 msg = _(
3107 'Cannot find configuration file in any of:\n'
3108 '\n'
3109 ' %s\n'
3110 'You may need to use the comand line option\n'
3111 '\n'
3112 ' --conf-file=<FILE>'
3113 ) % '\n '.join(candidates)
3114 gmGuiHelpers.gm_show_error(msg, _('Checking configuration files'))
3115 return False
3116
3117 _cfg.set_option(option = u'user_preferences_file', value = prefs_file)
3118 _log.info('user preferences file: %s', prefs_file)
3119
3120 return True
3121
3123
3124 from socket import error as SocketError
3125 from Gnumed.pycommon import gmScriptingListener
3126 from Gnumed.wxpython import gmMacro
3127
3128 slave_personality = gmTools.coalesce (
3129 _cfg.get (
3130 group = u'workplace',
3131 option = u'slave personality',
3132 source_order = [
3133 ('explicit', 'return'),
3134 ('workbase', 'return'),
3135 ('user', 'return'),
3136 ('system', 'return')
3137 ]
3138 ),
3139 u'gnumed-client'
3140 )
3141 _cfg.set_option(option = 'slave personality', value = slave_personality)
3142
3143
3144 port = int (
3145 gmTools.coalesce (
3146 _cfg.get (
3147 group = u'workplace',
3148 option = u'xml-rpc port',
3149 source_order = [
3150 ('explicit', 'return'),
3151 ('workbase', 'return'),
3152 ('user', 'return'),
3153 ('system', 'return')
3154 ]
3155 ),
3156 9999
3157 )
3158 )
3159 _cfg.set_option(option = 'xml-rpc port', value = port)
3160
3161 macro_executor = gmMacro.cMacroPrimitives(personality = slave_personality)
3162 global _scripting_listener
3163 try:
3164 _scripting_listener = gmScriptingListener.cScriptingListener(port = port, macro_executor = macro_executor)
3165 except SocketError, e:
3166 _log.exception('cannot start GNUmed XML-RPC server')
3167 gmGuiHelpers.gm_show_error (
3168 aMessage = (
3169 'Cannot start the GNUmed server:\n'
3170 '\n'
3171 ' [%s]'
3172 ) % e,
3173 aTitle = _('GNUmed startup')
3174 )
3175 return False
3176
3177 return True
3178
3199
3201 if gmI18N.system_locale is None or gmI18N.system_locale == '':
3202 _log.warning("system locale is undefined (probably meaning 'C')")
3203 return True
3204
3205
3206 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': u"select i18n.get_curr_lang() as lang"}])
3207 db_lang = rows[0]['lang']
3208
3209 if db_lang is None:
3210 _log.debug("database locale currently not set")
3211 msg = _(
3212 "There is no language selected in the database for user [%s].\n"
3213 "Your system language is currently set to [%s].\n\n"
3214 "Do you want to set the database language to '%s' ?\n\n"
3215 ) % (_provider['db_user'], gmI18N.system_locale, gmI18N.system_locale)
3216 checkbox_msg = _('Remember to ignore missing language')
3217 else:
3218 _log.debug("current database locale: [%s]" % db_lang)
3219 msg = _(
3220 "The currently selected database language ('%s') does\n"
3221 "not match the current system language ('%s').\n"
3222 "\n"
3223 "Do you want to set the database language to '%s' ?\n"
3224 ) % (db_lang, gmI18N.system_locale, gmI18N.system_locale)
3225 checkbox_msg = _('Remember to ignore language mismatch')
3226
3227
3228 if db_lang == gmI18N.system_locale_level['full']:
3229 _log.debug('Database locale (%s) up to date.' % db_lang)
3230 return True
3231 if db_lang == gmI18N.system_locale_level['country']:
3232 _log.debug('Database locale (%s) matches system locale (%s) at country level.' % (db_lang, gmI18N.system_locale))
3233 return True
3234 if db_lang == gmI18N.system_locale_level['language']:
3235 _log.debug('Database locale (%s) matches system locale (%s) at language level.' % (db_lang, gmI18N.system_locale))
3236 return True
3237
3238 _log.warning('database locale [%s] does not match system locale [%s]' % (db_lang, gmI18N.system_locale))
3239
3240
3241 ignored_sys_lang = _cfg.get (
3242 group = u'backend',
3243 option = u'ignored mismatching system locale',
3244 source_order = [('explicit', 'return'), ('local', 'return'), ('user', 'return'), ('system', 'return')]
3245 )
3246
3247
3248 if gmI18N.system_locale == ignored_sys_lang:
3249 _log.info('configured to ignore system-to-database locale mismatch')
3250 return True
3251
3252
3253 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
3254 None,
3255 -1,
3256 caption = _('Checking database language settings'),
3257 question = msg,
3258 button_defs = [
3259 {'label': _('Set'), 'tooltip': _('Set your database language to [%s].') % gmI18N.system_locale, 'default': True},
3260 {'label': _("Don't set"), 'tooltip': _('Do not set your database language now.'), 'default': False}
3261 ],
3262 show_checkbox = True,
3263 checkbox_msg = checkbox_msg,
3264 checkbox_tooltip = _(
3265 'Checking this will make GNUmed remember your decision\n'
3266 'until the system language is changed.\n'
3267 '\n'
3268 'You can also reactivate this inquiry by removing the\n'
3269 'corresponding "ignore" option from the configuration file\n'
3270 '\n'
3271 ' [%s]'
3272 ) % _cfg.get(option = 'user_preferences_file')
3273 )
3274 decision = dlg.ShowModal()
3275 remember_ignoring_problem = dlg._CHBOX_dont_ask_again.GetValue()
3276 dlg.Destroy()
3277
3278 if decision == wx.ID_NO:
3279 if not remember_ignoring_problem:
3280 return True
3281 _log.info('User did not want to set database locale. Ignoring mismatch next time.')
3282 gmCfg2.set_option_in_INI_file (
3283 filename = _cfg.get(option = 'user_preferences_file'),
3284 group = 'backend',
3285 option = 'ignored mismatching system locale',
3286 value = gmI18N.system_locale
3287 )
3288 return True
3289
3290
3291 for lang in [gmI18N.system_locale_level['full'], gmI18N.system_locale_level['country'], gmI18N.system_locale_level['language']]:
3292 if len(lang) > 0:
3293
3294
3295 rows, idx = gmPG2.run_rw_queries (
3296 link_obj = None,
3297 queries = [{'cmd': u'select i18n.set_curr_lang(%s)', 'args': [lang]}],
3298 return_data = True
3299 )
3300 if rows[0][0]:
3301 _log.debug("Successfully set database language to [%s]." % lang)
3302 else:
3303 _log.error('Cannot set database language to [%s].' % lang)
3304 continue
3305 return True
3306
3307
3308 _log.info('forcing database language to [%s]', gmI18N.system_locale_level['country'])
3309 gmPG2.run_rw_queries(queries = [{
3310 'cmd': u'select i18n.force_curr_lang(%s)',
3311 'args': [gmI18N.system_locale_level['country']]
3312 }])
3313
3314 return True
3315
3317 try:
3318 kwargs['originated_in_database']
3319 print '==> got notification from database "%s":' % kwargs['signal']
3320 except KeyError:
3321 print '==> received signal from client: "%s"' % kwargs['signal']
3322
3323 del kwargs['signal']
3324 for key in kwargs.keys():
3325
3326 try: print ' [%s]: %s' % (key, kwargs[key])
3327 except: print 'cannot print signal information'
3328
3332
3339
3341
3342 if _cfg.get(option = 'debug'):
3343 gmDispatcher.connect(receiver = _signal_debugging_monitor)
3344 _log.debug('gmDispatcher signal monitor activated')
3345
3346 setup_safe_wxEndBusyCursor()
3347
3348 wx.InitAllImageHandlers()
3349
3350
3351
3352 app = gmApp(redirect = False, clearSigInt = False)
3353 app.MainLoop()
3354
3355
3356
3357 if __name__ == '__main__':
3358
3359 from GNUmed.pycommon import gmI18N
3360 gmI18N.activate_locale()
3361 gmI18N.install_domain()
3362
3363 _log.info('Starting up as main module.')
3364 main()
3365
3366
3367