blob: ea11e2426ffe26e5cfe0f76a6f4a24b666cb6e2e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.editors.text;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.osgi.service.prefs.BackingStoreException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.RGB;
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.Label;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.preference.ColorSelector;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.ui.internal.editors.text.OverlayPreferenceStore.OverlayKey;
import org.eclipse.ui.texteditor.AnnotationPreference;
import org.eclipse.ui.texteditor.MarkerAnnotationPreferences;
import org.eclipse.ui.editors.text.EditorsUI;
/**
* Configures the linked mode preferences. The preferences belong to
* org.eclipse.ui.editors. However, as they are chiefly used in the java editor,
* we keep the preferences here for the time being.
*
* @since 3.2 (in jdt.ui since 3.1)
*/
class LinkedModeConfigurationBlock implements IPreferenceConfigurationBlock {
private static final String EXIT= "org.eclipse.ui.internal.workbench.texteditor.link.exit"; //$NON-NLS-1$
private static final String TARGET= "org.eclipse.ui.internal.workbench.texteditor.link.target"; //$NON-NLS-1$
private static final String MASTER= "org.eclipse.ui.internal.workbench.texteditor.link.master"; //$NON-NLS-1$
private static final String SLAVE= "org.eclipse.ui.internal.workbench.texteditor.link.slave"; //$NON-NLS-1$
private static final class ListItem {
final String label;
final String colorKey;
final String highlightKey;
final String textStyleKey;
final String textKey;
final List<String[]> validStyles;
ListItem(String label, String colorKey, String textKey, String highlightKey, String textStyleKey, List<String[]> validStyles) {
this.label= label;
this.colorKey= colorKey;
this.highlightKey= highlightKey;
this.textKey= textKey;
this.textStyleKey= textStyleKey;
this.validStyles= validStyles;
}
}
private static final class ItemContentProvider implements IStructuredContentProvider {
@Override
public Object[] getElements(Object inputElement) {
return (ListItem[]) inputElement;
}
@Override
public void dispose() {
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
private static final class ItemLabelProvider extends LabelProvider {
@Override
public String getText(Object element) {
return ((ListItem) element).label;
}
}
private static class ArrayLabelProvider extends LabelProvider {
@Override
public String getText(Object element) {
return ((String[]) element)[0].toString();
}
}
final static String[] HIGHLIGHT= new String[] {TextEditorMessages.LinkedModeConfigurationBlock_HIGHLIGHT, "unused"}; //$NON-NLS-1$
final static String[] UNDERLINE= new String[] {TextEditorMessages.LinkedModeConfigurationBlock_UNDERLINE, AnnotationPreference.STYLE_UNDERLINE};
final static String[] BOX= new String[] {TextEditorMessages.LinkedModeConfigurationBlock_BOX, AnnotationPreference.STYLE_BOX};
final static String[] DASHED_BOX= new String[] {TextEditorMessages.LinkedModeConfigurationBlock_DASHED_BOX, AnnotationPreference.STYLE_DASHED_BOX};
final static String[] IBEAM= new String[] {TextEditorMessages.LinkedModeConfigurationBlock_IBEAM, AnnotationPreference.STYLE_IBEAM};
final static String[] SQUIGGLES= new String[] {TextEditorMessages.LinkedModeConfigurationBlock_SQUIGGLES, AnnotationPreference.STYLE_SQUIGGLES};
private ColorSelector fAnnotationForegroundColorEditor;
private Button fShowInTextCheckBox;
private StructuredViewer fAnnotationTypeViewer;
private final ListItem[] fListModel;
private ComboViewer fDecorationViewer;
private FontMetrics fFontMetrics;
protected static final int INDENT= 20;
private OverlayPreferenceStore fStore;
private ArrayList<SelectionListener> fMasterSlaveListeners= new ArrayList<>();
private OverlayPreferenceStore getPreferenceStore() {
return fStore;
}
public LinkedModeConfigurationBlock(OverlayPreferenceStore store) {
fStore= store;
final MarkerAnnotationPreferences prefs= EditorsPlugin.getDefault().getMarkerAnnotationPreferences();
getPreferenceStore().addKeys(createOverlayStoreKeys(prefs));
fListModel= createAnnotationTypeListModel(prefs);
}
private OverlayPreferenceStore.OverlayKey[] createOverlayStoreKeys(MarkerAnnotationPreferences preferences) {
ArrayList<OverlayKey> overlayKeys= new ArrayList<>();
Iterator<AnnotationPreference> e= preferences.getAnnotationPreferences().iterator();
while (e.hasNext()) {
AnnotationPreference info= e.next();
if (isLinkedModeAnnotation(info)) {
overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, info.getColorPreferenceKey()));
overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, info.getTextPreferenceKey()));
overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, info.getTextStylePreferenceKey()));
overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, info.getHighlightPreferenceKey()));
}
}
OverlayPreferenceStore.OverlayKey[] keys= new OverlayPreferenceStore.OverlayKey[overlayKeys.size()];
overlayKeys.toArray(keys);
return keys;
}
private boolean isLinkedModeAnnotation(AnnotationPreference info) {
final Object type= info.getAnnotationType();
return type.equals(MASTER)
|| (type.equals(SLAVE))
|| (type.equals(TARGET))
|| (type.equals(EXIT));
}
private ListItem[] createAnnotationTypeListModel(MarkerAnnotationPreferences preferences) {
ArrayList<ListItem> listModelItems= new ArrayList<>();
Iterator<AnnotationPreference> e= preferences.getAnnotationPreferences().iterator();
while (e.hasNext()) {
AnnotationPreference info= e.next();
if (isLinkedModeAnnotation(info)) {
String label= info.getPreferenceLabel();
List<String[]> styles= getStyles(info.getAnnotationType());
listModelItems.add(new ListItem(label, info.getColorPreferenceKey(), info.getTextPreferenceKey(), info.getHighlightPreferenceKey(), info.getTextStylePreferenceKey(), styles));
}
}
ListItem[] items= new ListItem[listModelItems.size()];
listModelItems.toArray(items);
return items;
}
private List<String[]> getStyles(Object type) {
if (type.equals(MASTER))
return Arrays.asList(BOX, DASHED_BOX, HIGHLIGHT, UNDERLINE, SQUIGGLES);
if (type.equals(SLAVE))
return Arrays.asList(BOX, DASHED_BOX, HIGHLIGHT, UNDERLINE, SQUIGGLES);
if (type.equals(TARGET))
return Arrays.asList(BOX, DASHED_BOX, HIGHLIGHT, UNDERLINE, SQUIGGLES);
if (type.equals(EXIT))
return Arrays.asList(new String[][] { IBEAM });
return new ArrayList<>();
}
/**
* Creates page for hover preferences.
*
* @param parent the parent composite
* @return the control for the preference page
*/
@Override
public Control createControl(Composite parent) {
OverlayPreferenceStore store= getPreferenceStore();
store.load();
store.start();
initializeDialogUnits(parent);
Composite composite= new Composite(parent, SWT.NONE);
GridLayout layout= new GridLayout();
layout.marginHeight= 0;
layout.marginWidth= 0;
composite.setLayout(layout);
Label label= new Label(composite, SWT.LEFT);
label.setText(TextEditorMessages.LinkedModeConfigurationBlock_annotationPresentationOptions);
GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
label.setLayoutData(gd);
Composite editorComposite= new Composite(composite, SWT.NONE);
layout= new GridLayout();
layout.numColumns= 2;
layout.marginHeight= 0;
layout.marginWidth= 0;
editorComposite.setLayout(layout);
gd= new GridData(SWT.FILL, SWT.FILL, true, true);
editorComposite.setLayoutData(gd);
fAnnotationTypeViewer= new TableViewer(editorComposite, SWT.SINGLE | SWT.V_SCROLL | SWT.BORDER);
fAnnotationTypeViewer.setLabelProvider(new ItemLabelProvider());
fAnnotationTypeViewer.setContentProvider(new ItemContentProvider());
gd= new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false);
gd.heightHint= convertHeightInCharsToPixels(5);
fAnnotationTypeViewer.getControl().setLayoutData(gd);
Composite optionsComposite= new Composite(editorComposite, SWT.NONE);
layout= new GridLayout();
layout.marginHeight= 0;
layout.marginWidth= 0;
layout.numColumns= 2;
optionsComposite.setLayout(layout);
optionsComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false));
// we only allow to set either "show in text" or "highlight in text", but not both
fShowInTextCheckBox= new Button(optionsComposite, SWT.CHECK);
fShowInTextCheckBox.setText(TextEditorMessages.LinkedModeConfigurationBlock_labels_showIn);
gd= new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalAlignment= GridData.BEGINNING;
fShowInTextCheckBox.setLayoutData(gd);
fDecorationViewer= new ComboViewer(optionsComposite, SWT.READ_ONLY);
fDecorationViewer.setContentProvider(ArrayContentProvider.getInstance());
fDecorationViewer.setLabelProvider(new ArrayLabelProvider());
fDecorationViewer.setComparator(new ViewerComparator(Collator.getInstance()));
gd= new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalAlignment= GridData.BEGINNING;
fDecorationViewer.getControl().setLayoutData(gd);
fDecorationViewer.setInput(new Object[] {HIGHLIGHT, SQUIGGLES, BOX, DASHED_BOX, UNDERLINE, IBEAM});
label= new Label(optionsComposite, SWT.LEFT);
label.setText(TextEditorMessages.LinkedModeConfigurationBlock_color);
gd= new GridData();
gd.horizontalAlignment= GridData.BEGINNING;
label.setLayoutData(gd);
fAnnotationForegroundColorEditor= new ColorSelector(optionsComposite);
Button foregroundColorButton= fAnnotationForegroundColorEditor.getButton();
gd= new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalAlignment= GridData.BEGINNING;
foregroundColorButton.setLayoutData(gd);
createDependency(fShowInTextCheckBox, new Control[] {label, foregroundColorButton});
fAnnotationTypeViewer.addSelectionChangedListener(event -> handleAnnotationListSelection());
fShowInTextCheckBox.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
// do nothing
}
@Override
public void widgetSelected(SelectionEvent e) {
ListItem item= getSelectedItem();
final boolean value= fShowInTextCheckBox.getSelection();
if (value) {
// enable whatever is in the combo
String[] decoration= (String[]) fDecorationViewer.getStructuredSelection().getFirstElement();
if (HIGHLIGHT.equals(decoration))
getPreferenceStore().setValue(item.highlightKey, true);
else
getPreferenceStore().setValue(item.textKey, true);
} else {
// disable both
getPreferenceStore().setValue(item.textKey, false);
getPreferenceStore().setValue(item.highlightKey, false);
}
getPreferenceStore().setValue(item.textKey, value);
updateDecorationViewer(item, false);
}
});
foregroundColorButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
// do nothing
}
@Override
public void widgetSelected(SelectionEvent e) {
ListItem item= getSelectedItem();
PreferenceConverter.setValue(getPreferenceStore(), item.colorKey, fAnnotationForegroundColorEditor.getColorValue());
}
});
fDecorationViewer.addSelectionChangedListener(event -> {
String[] decoration= (String[]) fDecorationViewer.getStructuredSelection().getFirstElement();
ListItem item= getSelectedItem();
if (fShowInTextCheckBox.getSelection()) {
if (HIGHLIGHT.equals(decoration)) {
getPreferenceStore().setValue(item.highlightKey, true);
getPreferenceStore().setValue(item.textKey, false);
getPreferenceStore().setValue(item.textStyleKey, AnnotationPreference.STYLE_NONE);
} else {
getPreferenceStore().setValue(item.highlightKey, false);
getPreferenceStore().setValue(item.textKey, true);
getPreferenceStore().setValue(item.textStyleKey, decoration[1]);
}
}
});
return composite;
}
@Override
public void applyData(Object data) {
}
/**
* Returns the number of pixels corresponding to the width of the given number of characters.
* <p>
* This method may only be called after <code>initializeDialogUnits</code> has been called.
* </p>
* <p>
* Clients may call this framework method, but should not override it.
* </p>
*
* @param chars the number of characters
* @return the number of pixels
*/
protected int convertWidthInCharsToPixels(int chars) {
// test for failure to initialize for backward compatibility
if (fFontMetrics == null)
return 0;
return Dialog.convertWidthInCharsToPixels(fFontMetrics, chars);
}
/**
* Returns the number of pixels corresponding to the height of the given number of characters.
* <p>
* This method may only be called after <code>initializeDialogUnits</code> has been called.
* </p>
* <p>
* Clients may call this framework method, but should not override it.
* </p>
*
* @param chars the number of characters
* @return the number of pixels
*/
protected int convertHeightInCharsToPixels(int chars) {
// test for failure to initialize for backward compatibility
if (fFontMetrics == null)
return 0;
return Dialog.convertHeightInCharsToPixels(fFontMetrics, chars);
}
/**
* Initializes the computation of horizontal and vertical dialog units based on the size of
* current font.
* <p>
* This method must be called before any of the dialog unit based conversion methods are called.
* </p>
*
* @param testControl a control from which to obtain the current font
*/
protected void initializeDialogUnits(Control testControl) {
// Compute and store a font metric
GC gc = new GC(testControl);
gc.setFont(JFaceResources.getDialogFont());
fFontMetrics = gc.getFontMetrics();
gc.dispose();
}
private void handleAnnotationListSelection() {
ListItem item= getSelectedItem();
RGB rgb= PreferenceConverter.getColor(getPreferenceStore(), item.colorKey);
fAnnotationForegroundColorEditor.setColorValue(rgb);
boolean highlight= item.highlightKey == null ? false : getPreferenceStore().getBoolean(item.highlightKey);
boolean showInText = item.textKey == null ? false : getPreferenceStore().getBoolean(item.textKey);
fShowInTextCheckBox.setSelection(showInText || highlight);
updateDecorationViewer(item, true);
}
@Override
public void initialize() {
initializeFields();
fAnnotationTypeViewer.setInput(fListModel);
fAnnotationTypeViewer.getControl().getDisplay().asyncExec(() -> {
if (fAnnotationTypeViewer != null && !fAnnotationTypeViewer.getControl().isDisposed()) {
fAnnotationTypeViewer.setSelection(new StructuredSelection(fListModel[0]));
initializeFields();
}
});
}
private ListItem getSelectedItem() {
return (ListItem) fAnnotationTypeViewer.getStructuredSelection().getFirstElement();
}
private void updateDecorationViewer(ListItem item, boolean changed) {
// decoration selection: if the checkbox is enabled, there is
// only one case where the combo is not enabled: if both the highlight and textStyle keys are null
final boolean enabled= fShowInTextCheckBox.getSelection() && !(item.highlightKey == null && item.textStyleKey == null);
fDecorationViewer.getControl().setEnabled(enabled);
if (changed) {
String[] selection= null;
ArrayList<String[]> list= new ArrayList<>();
list.addAll(item.validStyles);
if (getPreferenceStore().getBoolean(item.highlightKey))
selection= HIGHLIGHT;
// set selection
if (selection == null) {
String val= getPreferenceStore().getString(item.textStyleKey);
for (String[] elem : list) {
if (elem[1].equals(val)) {
selection= elem;
break;
}
}
}
fDecorationViewer.setInput(list.toArray(new Object[list.size()]));
if (selection == null)
selection= list.get(0);
fDecorationViewer.setSelection(new StructuredSelection((Object) selection), true);
}
}
@Override
public void performOk() {
getPreferenceStore().propagate();
try {
Platform.getPreferencesService().getRootNode().node(InstanceScope.SCOPE).node(EditorsUI.PLUGIN_ID).flush();
} catch (BackingStoreException e) {
EditorsPlugin.log(e);
}
}
@Override
public void performDefaults() {
getPreferenceStore().loadDefaults();
/*
* Only call super after updating fShowInTextCheckBox, so that
* the master-slave dependencies get properly updated.
*/
handleAnnotationListSelection();
initializeFields();
}
@Override
public void dispose() {
OverlayPreferenceStore store= getPreferenceStore();
if (store != null) {
store.stop();
}
}
protected void createDependency(final Button master, final Control slave) {
createDependency(master, new Control[] {slave});
}
protected void createDependency(final Button master, final Control[] slaves) {
Assert.isTrue(slaves.length > 0);
indent(slaves[0]);
SelectionListener listener= new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
boolean state= master.getSelection();
for (Control slave : slaves) {
slave.setEnabled(state);
}
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {}
};
master.addSelectionListener(listener);
fMasterSlaveListeners.add(listener);
}
protected static void indent(Control control) {
((GridData) control.getLayoutData()).horizontalIndent+= INDENT;
}
private void initializeFields() {
// Update slaves
Iterator<SelectionListener> iter= fMasterSlaveListeners.iterator();
while (iter.hasNext()) {
SelectionListener listener= iter.next();
listener.widgetSelected(null);
}
}
@Override
public boolean canPerformOk() {
return true;
}
}