| /*=============================================================================# |
| # Copyright (c) 2009, 2018 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.rj.eclient.graphics; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.osgi.service.prefs.BackingStoreException; |
| |
| import org.eclipse.core.databinding.AggregateValidationStatus; |
| import org.eclipse.core.databinding.DataBindingContext; |
| import org.eclipse.core.databinding.UpdateValueStrategy; |
| import org.eclipse.core.databinding.observable.Realm; |
| import org.eclipse.core.databinding.observable.value.IObservableValue; |
| import org.eclipse.core.databinding.observable.value.ValueChangeEvent; |
| import org.eclipse.core.databinding.observable.value.WritableValue; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.preferences.DefaultScope; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.core.runtime.preferences.IScopeContext; |
| import org.eclipse.core.runtime.preferences.InstanceScope; |
| import org.eclipse.jface.databinding.swt.WidgetProperties; |
| import org.eclipse.jface.preference.PreferencePage; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.viewers.ArrayContentProvider; |
| import org.eclipse.jface.viewers.ComboViewer; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTError; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.FontData; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.FontDialog; |
| import org.eclipse.swt.widgets.Group; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.ui.IWorkbench; |
| import org.eclipse.ui.IWorkbenchPreferencePage; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| |
| import org.eclipse.statet.ecommons.databinding.DecimalValidator; |
| import org.eclipse.statet.ecommons.ui.components.StatusInfo; |
| import org.eclipse.statet.ecommons.ui.dialogs.DialogUtils; |
| import org.eclipse.statet.ecommons.ui.util.LayoutUtil; |
| |
| |
| /** |
| * Preference page with options to configure R graphic options: |
| * <ul> |
| * <li>Default font families ('serif', 'sans', 'mono')</li> |
| * </ul> |
| * |
| * The page is not registered by this plug-in. |
| */ |
| public class RGraphicsPreferencePage extends PreferencePage implements IWorkbenchPreferencePage { |
| |
| |
| public static double[] parseDPI(final String prefValue) { |
| if (prefValue != null) { |
| final String[] strings= prefValue.split(","); |
| if (strings.length == 2) { |
| try { |
| return new double[] { |
| Double.parseDouble(strings[0]), |
| Double.parseDouble(strings[1]), |
| }; |
| } |
| catch (final Exception e) {} |
| } |
| } |
| return null; |
| } |
| |
| |
| private static class FontPref { |
| |
| final String prefKey; |
| |
| String defaultName; |
| String currentName; |
| |
| Font currentFont; |
| |
| Label valueLabel; |
| |
| |
| public FontPref(final String key) { |
| this.prefKey= key; |
| } |
| |
| |
| @Override |
| public int hashCode() { |
| return this.prefKey.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(final Object obj) { |
| return (this == obj |
| || (obj instanceof FontPref |
| && this.prefKey.equals(((FontPref) obj).prefKey) )); |
| } |
| |
| } |
| |
| private static class Encoding { |
| |
| final String label; |
| final String prefValue; |
| |
| |
| public Encoding(final String label, final String prefValue) { |
| this.label= label; |
| this.prefValue= prefValue; |
| } |
| |
| |
| @Override |
| public int hashCode() { |
| return this.prefValue.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(final Object obj) { |
| return (this == obj |
| || (obj instanceof Encoding |
| && this.prefValue.equals(((Encoding) obj).prefValue) )); |
| } |
| |
| @Override |
| public String toString() { |
| return this.label.toString(); |
| }; |
| |
| } |
| |
| private static final Encoding[] SYMBOL_ENCODINGS= new Encoding[] { |
| new Encoding("Unicode", "Unicode"), |
| new Encoding("Adobe Symbol", "AdobeSymbol"), |
| }; |
| |
| private static final Encoding SYMBOL_ENCODING_DEFAULT= SYMBOL_ENCODINGS[1]; |
| |
| |
| private DataBindingContext dbc; |
| |
| private FontPref serifPref; |
| private FontPref sansPref; |
| private FontPref monoPref; |
| private FontPref symbolFontPref; |
| private FontPref[] fontPrefs; |
| private Button symbolUseControl; |
| private ComboViewer symbolEncodingControl; |
| private Control[] symbolChildControls; |
| |
| private final int size= 10; |
| |
| private Button customDpiControl; |
| private Composite customDpiComposite; |
| private Text customHDpiControl; |
| private Text customVDpiControl; |
| |
| private boolean customEnabled; |
| private IObservableValue<Double> customHDpiVisibleValue; |
| private IObservableValue<Double> customVDpiVisibleValue; |
| private double customHDpiUserValue; |
| private double customVDpiUserValue; |
| |
| |
| /** |
| * Created via extension point |
| */ |
| public RGraphicsPreferencePage() { |
| } |
| |
| |
| @Override |
| public void init(final IWorkbench workbench) { |
| this.serifPref= new FontPref(RGraphics.PREF_FONTS_SERIF_FONTNAME_KEY); |
| this.sansPref= new FontPref(RGraphics.PREF_FONTS_SANS_FONTNAME_KEY); |
| this.monoPref= new FontPref(RGraphics.PREF_FONTS_MONO_FONTNAME_KEY); |
| this.symbolFontPref= new FontPref(RGraphics.PREF_FONTS_SYMBOL_FONTNAME_KEY); |
| this.fontPrefs= new FontPref[] { this.serifPref, this.sansPref, this.monoPref, this.symbolFontPref }; |
| |
| final IEclipsePreferences node= DefaultScope.INSTANCE.getNode(RGraphics.FONTS_PREF_QUALIFIER); |
| for (final FontPref pref : this.fontPrefs) { |
| pref.defaultName= (node != null) ? node.get(pref.prefKey, "") : ""; |
| } |
| } |
| |
| @Override |
| protected Control createContents(final Composite parent) { |
| final Composite pageComposite= new Composite(parent, SWT.NONE); |
| pageComposite.setLayout(LayoutUtil.applyCompositeDefaults(new GridLayout(), 1)); |
| |
| final Group displayGroup= createDisplayGroup(pageComposite); |
| displayGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); |
| |
| final Group fontGroup= createFontGroup(pageComposite); |
| fontGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); |
| |
| initBindings(); |
| loadDisplayOptions(); |
| loadFontOptions(); |
| |
| applyDialogFont(pageComposite); |
| |
| return pageComposite; |
| } |
| |
| protected Group createDisplayGroup(final Composite parent) { |
| final Group group= new Group(parent, SWT.NONE); |
| group.setText("Display:"); |
| group.setLayout(LayoutUtil.applyGroupDefaults(new GridLayout(), 2)); |
| |
| this.customDpiControl= new Button(group, SWT.CHECK); |
| this.customDpiControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); |
| this.customDpiControl.setText("Use custom DPI (instead of system setting):"); |
| |
| this.customDpiComposite= new Composite(group, SWT.NONE); |
| |
| { final GridData gd= new GridData(SWT.FILL, SWT.FILL, true, false); |
| gd.horizontalIndent= LayoutUtil.defaultIndent(); |
| this.customDpiComposite.setLayoutData(gd); |
| this.customDpiComposite.setLayout(LayoutUtil.applyCompositeDefaults(new GridLayout(), 2)); |
| } |
| |
| { final Label label= new Label(this.customDpiComposite, SWT.LEFT); |
| label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); |
| label.setText("&Horizontal (x):"); |
| } |
| { final Text text= new Text(this.customDpiComposite, SWT.BORDER | SWT.RIGHT); |
| final GridData gd= new GridData(SWT.LEFT, SWT.CENTER, false, false); |
| gd.widthHint= LayoutUtil.hintWidth(text, 8); |
| text.setLayoutData(gd); |
| this.customHDpiControl= text; |
| } |
| { final Label label= new Label(this.customDpiComposite, SWT.LEFT); |
| label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); |
| label.setText("&Vertical (y):"); |
| } |
| { final Text text= new Text(this.customDpiComposite, SWT.BORDER | SWT.RIGHT); |
| final GridData gd= new GridData(SWT.LEFT, SWT.CENTER, false, false); |
| gd.widthHint= LayoutUtil.hintWidth(text, 8); |
| text.setLayoutData(gd); |
| this.customVDpiControl= text; |
| } |
| |
| this.customDpiControl.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(final SelectionEvent e) { |
| updateDisplayGroup(); |
| } |
| }); |
| |
| return group; |
| } |
| |
| private void updateDisplayGroup() { |
| this.customEnabled= this.customDpiControl.getSelection(); |
| DialogUtils.setEnabled(this.customDpiComposite, null, this.customEnabled); |
| |
| final double hvalue; |
| final double vvalue; |
| if (!this.customEnabled || this.customHDpiUserValue <= 0 || this.customVDpiUserValue <= 0) { |
| final Point dpi= Display.getDefault().getDPI(); |
| hvalue= dpi.x; |
| vvalue= dpi.y; |
| if (this.customEnabled) { |
| this.customHDpiUserValue= hvalue; |
| this.customVDpiUserValue= vvalue; |
| } |
| } |
| else { |
| hvalue= this.customHDpiUserValue; |
| vvalue= this.customVDpiUserValue; |
| } |
| this.customHDpiVisibleValue.setValue(hvalue); |
| this.customVDpiVisibleValue.setValue(vvalue); |
| } |
| |
| protected Group createFontGroup(final Composite parent) { |
| final Group group= new Group(parent, SWT.NONE); |
| group.setText("Fonts:"); |
| group.setLayout(LayoutUtil.applyGroupDefaults(new GridLayout(), 3)); |
| |
| addFont(group, this.serifPref, "Default &Serif Font ('serif'):"); |
| addFont(group, this.sansPref, "Default S&ansserif Font ('sans'):"); |
| addFont(group, this.monoPref, "Default &Monospace Font ('mono'):"); |
| |
| LayoutUtil.addSmallFiller(group, false); |
| |
| this.symbolUseControl= new Button(group, SWT.CHECK); |
| this.symbolUseControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 3, 1)); |
| this.symbolUseControl.setText("Use special S&ymbol Font"); |
| final List<Control> symbolControls= new ArrayList<>(); |
| addFont(group, this.symbolFontPref, "Symbol &Font:", |
| LayoutUtil.defaultIndent(), symbolControls ); |
| { final Label label= new Label(group, SWT.NONE); |
| final GridData gd= new GridData(SWT.FILL, SWT.CENTER, false, false); |
| gd.horizontalIndent= LayoutUtil.defaultIndent(); |
| label.setLayoutData(gd); |
| label.setText("Encoding:"); |
| symbolControls.add(label); |
| } |
| { this.symbolEncodingControl= new ComboViewer(group, SWT.DROP_DOWN | SWT.READ_ONLY); |
| final GridData gd= new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1); |
| gd.widthHint= LayoutUtil.hintWidth(this.symbolEncodingControl.getCombo(), 15); |
| this.symbolEncodingControl.getControl().setLayoutData(gd); |
| this.symbolEncodingControl.setContentProvider(new ArrayContentProvider()); |
| this.symbolEncodingControl.setInput(SYMBOL_ENCODINGS); |
| symbolControls.add(this.symbolEncodingControl.getControl()); |
| } |
| this.symbolChildControls= symbolControls.toArray(new Control[symbolControls.size()]); |
| this.symbolUseControl.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(final SelectionEvent e) { |
| updateSymbolControls(); |
| } |
| }); |
| |
| return group; |
| } |
| |
| private void addFont(final Group group, final FontPref pref, final String text) { |
| addFont(group, pref, text, 0, null); |
| } |
| |
| private void addFont(final Group group, final FontPref pref, final String text, |
| final int indent, final List<Control> controls) { |
| final Label label= new Label(group, SWT.NONE); |
| final GridData gd= new GridData(SWT.FILL, SWT.CENTER, false, false); |
| gd.horizontalIndent= indent; |
| label.setLayoutData(gd); |
| label.setText(text); |
| |
| pref.valueLabel= new Label(group, SWT.BORDER); |
| pref.valueLabel.setBackground(label.getDisplay().getSystemColor(SWT.COLOR_WHITE)); |
| pref.valueLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); |
| |
| final Button button= new Button(group, SWT.PUSH); |
| button.setText("Edit..."); |
| button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); |
| button.addSelectionListener(new SelectionListener() { |
| @Override |
| public void widgetSelected(final SelectionEvent e) { |
| final FontDialog dialog= new FontDialog(button.getShell(), SWT.NONE); |
| dialog.setFontList((pref.currentFont != null) ? pref.currentFont.getFontData() : null); |
| final FontData result= dialog.open(); |
| if (result != null) { |
| set(pref, result.getName()); |
| } |
| } |
| @Override |
| public void widgetDefaultSelected(final SelectionEvent e) { |
| } |
| }); |
| |
| if (controls != null) { |
| controls.add(label); |
| controls.add(pref.valueLabel); |
| controls.add(button); |
| } |
| } |
| |
| protected void initBindings() { |
| final Realm realm= Realm.getDefault(); |
| this.dbc= new DataBindingContext(realm); |
| addBindings(this.dbc, realm); |
| |
| final AggregateValidationStatus validationStatus= new AggregateValidationStatus(this.dbc, AggregateValidationStatus.MAX_SEVERITY); |
| validationStatus.addValueChangeListener( |
| (final ValueChangeEvent<? extends IStatus> event) -> { |
| final IStatus currentStatus= event.diff.getNewValue(); |
| updateStatus(currentStatus); |
| } ); |
| } |
| |
| protected void addBindings(final DataBindingContext dbc, final Realm realm) { |
| this.customHDpiVisibleValue= new WritableValue<>(realm, 96.0, Double.class); |
| this.customVDpiVisibleValue= new WritableValue<>(realm, 96.0, Double.class); |
| |
| dbc.bindValue( |
| WidgetProperties.text(SWT.Modify).observe(this.customHDpiControl), |
| this.customHDpiVisibleValue, |
| new UpdateValueStrategy().setAfterGetValidator(new DecimalValidator(10.0, 10000.0, |
| "The value for Horizontal (x) DPI is invalid (10-10000)." )), |
| null ); |
| dbc.bindValue( |
| WidgetProperties.text(SWT.Modify).observe(this.customVDpiControl), |
| this.customVDpiVisibleValue, |
| new UpdateValueStrategy().setAfterGetValidator(new DecimalValidator(10.0, 10000.0, |
| "The value for Vertical (x) DPI is invalid (10-10000)." )), |
| null ); |
| |
| this.customHDpiVisibleValue.addValueChangeListener( |
| (final ValueChangeEvent<? extends Double> event) -> { |
| if (RGraphicsPreferencePage.this.customEnabled) { |
| RGraphicsPreferencePage.this.customHDpiUserValue= RGraphicsPreferencePage.this.customHDpiVisibleValue.getValue(); |
| } |
| } ); |
| this.customVDpiVisibleValue.addValueChangeListener( |
| (final ValueChangeEvent<? extends Double> event) -> { |
| if (RGraphicsPreferencePage.this.customEnabled) { |
| RGraphicsPreferencePage.this.customVDpiUserValue= RGraphicsPreferencePage.this.customVDpiVisibleValue.getValue(); |
| } |
| } ); |
| } |
| |
| protected void updateStatus(final IStatus status) { |
| setValid(!status.matches(IStatus.ERROR)); |
| StatusInfo.applyToStatusLine(this, status); |
| } |
| |
| protected void setCustomDpi(final String prefValue) { |
| final double[] dpi= parseDPI(prefValue); |
| if (dpi != null) { |
| this.customHDpiUserValue= dpi[0]; |
| this.customVDpiUserValue= dpi[1]; |
| this.customDpiControl.setSelection(true); |
| } |
| else { |
| this.customDpiControl.setSelection(false); |
| } |
| updateDisplayGroup(); |
| } |
| |
| protected void updateSymbolControls() { |
| DialogUtils.setEnabled(this.symbolChildControls, null, this.symbolUseControl.getSelection()); |
| } |
| |
| protected void set(final FontPref pref, final String value) { |
| if (value.equals(pref.currentName)) { |
| return; |
| } |
| pref.valueLabel.setText(""); |
| Font font; |
| try { |
| font= new Font(pref.valueLabel.getDisplay(), value, this.size, SWT.NONE); |
| if (pref != this.symbolFontPref) { |
| pref.valueLabel.setFont(font); |
| } |
| pref.valueLabel.setText(value); |
| } |
| catch (final SWTError e) { |
| font= JFaceResources.getDialogFont(); |
| pref.valueLabel.setFont(font); |
| pref.valueLabel.setText(value + " (not available)"); |
| } |
| if (pref.currentFont != null && !pref.currentFont.isDisposed()) { |
| pref.currentFont.dispose(); |
| } |
| pref.currentName= value; |
| pref.currentFont= font; |
| } |
| |
| @Override |
| protected void performDefaults() { |
| setCustomDpi(null); |
| this.symbolUseControl.setSelection(true); |
| this.symbolEncodingControl.setSelection(new StructuredSelection(SYMBOL_ENCODING_DEFAULT)); |
| for (final FontPref pref : this.fontPrefs) { |
| set(pref, pref.defaultName); |
| } |
| updateSymbolControls(); |
| super.performDefaults(); |
| } |
| |
| @Override |
| protected void performApply() { |
| saveDisplayOptions(true); |
| saveFontOptions(true); |
| } |
| |
| @Override |
| public boolean performOk() { |
| saveDisplayOptions(false); |
| saveFontOptions(false); |
| return true; |
| } |
| |
| |
| protected IScopeContext getScope() { |
| return InstanceScope.INSTANCE; |
| } |
| |
| protected void loadDisplayOptions() { |
| final IEclipsePreferences node= getScope().getNode(RGraphics.PREF_DISPLAY_QUALIFIER); |
| setCustomDpi(node.get(RGraphics.PREF_DISPLAY_CUSTOM_DPI_KEY, null)); |
| } |
| |
| protected void saveDisplayOptions(final boolean flush) { |
| final IEclipsePreferences node= getScope().getNode(RGraphics.PREF_DISPLAY_QUALIFIER); |
| if (this.customEnabled) { |
| node.put(RGraphics.PREF_DISPLAY_CUSTOM_DPI_KEY, |
| Double.toString(this.customHDpiUserValue) + "," + Double.toString(this.customVDpiUserValue)); |
| } |
| else { |
| node.remove(RGraphics.PREF_DISPLAY_CUSTOM_DPI_KEY); |
| } |
| if (flush) { |
| try { |
| node.flush(); |
| } |
| catch (final BackingStoreException e) { |
| StatusManager.getManager().handle(new Status(IStatus.ERROR, RGraphics.BUNDLE_ID, -1, |
| "An error occurred when storing R graphics display preferences.", e)); |
| } |
| } |
| } |
| |
| protected void loadFontOptions() { |
| if (this.fontPrefs != null) { |
| final IEclipsePreferences node= getScope().getNode(RGraphics.FONTS_PREF_QUALIFIER); |
| this.symbolUseControl.setSelection(node.getBoolean(RGraphics.PREF_FONTS_SYMBOL_USE_KEY, true)); |
| final String s= node.get(RGraphics.PREF_FONTS_SYMBOL_ENCODING_KEY, (String) null); |
| this.symbolEncodingControl.setSelection(new StructuredSelection((s != null) ? |
| new Encoding(null, s) : SYMBOL_ENCODING_DEFAULT )); |
| for (final FontPref pref : this.fontPrefs) { |
| final String value= node.get(pref.prefKey, ""); //$NON-NLS-1$ |
| set(pref, (value.length() > 0) ? value : pref.defaultName); |
| } |
| updateSymbolControls(); |
| } |
| } |
| |
| protected void saveFontOptions(final boolean flush) { |
| if (this.fontPrefs != null) { |
| final IEclipsePreferences node= getScope().getNode(RGraphics.FONTS_PREF_QUALIFIER); |
| node.putBoolean(RGraphics.PREF_FONTS_SYMBOL_USE_KEY, this.symbolUseControl.getSelection()); |
| final IStructuredSelection selection= (IStructuredSelection) this.symbolEncodingControl.getSelection(); |
| if (selection.getFirstElement() instanceof Encoding |
| && !SYMBOL_ENCODING_DEFAULT.equals(selection.getFirstElement())) { |
| node.put(RGraphics.PREF_FONTS_SYMBOL_ENCODING_KEY, |
| ((Encoding) selection.getFirstElement()).prefValue ); |
| } |
| else { |
| node.remove(RGraphics.PREF_FONTS_SYMBOL_ENCODING_KEY); |
| } |
| for (final FontPref pref : this.fontPrefs) { |
| if (pref.currentName == null || pref.currentName.equals(pref.defaultName)) { |
| node.remove(pref.prefKey); |
| } |
| else { |
| node.put(pref.prefKey, pref.currentName); |
| } |
| } |
| updateSymbolControls(); |
| if (flush) { |
| try { |
| node.flush(); |
| } |
| catch (final BackingStoreException e) { |
| StatusManager.getManager().handle(new Status(IStatus.ERROR, RGraphics.BUNDLE_ID, -1, |
| "An error occurred when storing R graphics font preferences.", e)); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| if (this.dbc != null) { |
| this.dbc.dispose(); |
| this.dbc= null; |
| } |
| if (this.fontPrefs != null) { |
| for (final FontPref pref : this.fontPrefs) { |
| if (pref.currentFont != null && !pref.currentFont.isDisposed()) { |
| pref.currentFont.dispose(); |
| pref.currentFont= null; |
| } |
| } |
| } |
| super.dispose(); |
| } |
| |
| } |