blob: 86319c879649bd75b9e994316fb7263b78025f7f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Cornel Izbasa <cizbasa@info.uvt.ro> - Bug 436247
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 440136, 472654
* Robert Roth <robert.roth.off@gmail.com> - Bugs 274005, 456291
* Mickael Istria (Red Hat Inc.) - Theme and fontregistry rather than pref
* Patrik Suzzi <psuzzi@gmail.com> - Bug 489250
*******************************************************************************/
package org.eclipse.ui.internal.themes;
import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;
import static org.eclipse.ui.internal.themes.WorkbenchThemeManager.EMPTY_COLOR_VALUE;
import com.ibm.icu.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.css.swt.theme.IThemeEngine;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.StringConverter;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.ColorDialog;
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.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
import org.eclipse.ui.internal.Workbench;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.util.PrefUtil;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;
import org.eclipse.ui.themes.IThemePreview;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
/**
* Preference page for management of system colors, gradients and fonts.
*
* @since 3.0
*/
public final class ColorsAndFontsPreferencePage extends PreferencePage
implements IWorkbenchPreferencePage {
private static final String SELECTED_ELEMENT_PREF = "ColorsAndFontsPreferencePage.selectedElement"; //$NON-NLS-1$
/**
* The preference that stores the expanded state.
*/
private static final String EXPANDED_ELEMENTS_PREF = "ColorsAndFontsPreferencePage.expandedCategories"; //$NON-NLS-1$
/**
* The token that separates expanded elements in EXPANDED_ELEMENTS_PREF.
*/
private static final String EXPANDED_ELEMENTS_TOKEN = "\t"; //$NON-NLS-1$
/**
* Marks category tokens in EXPANDED_ELEMENTS_PREF and SELECTED_ELEMENT_PREF.
*/
private static final char MARKER_CATEGORY = 'T';
/**
* Marks color tokens in EXPANDED_ELEMENTS_PREF and SELECTED_ELEMENT_PREF.
*/
private static final char MARKER_COLOR = 'C';
/**
* Marks font tokens in EXPANDED_ELEMENTS_PREF and SELECTED_ELEMENT_PREF.
*/
private static final char MARKER_FONT = 'F';
private static final int DEFINITION_NOT_AVAIL_COLOR = SWT.COLOR_WIDGET_LIGHT_SHADOW;
private class ThemeContentProvider implements ITreeContentProvider {
private IThemeRegistry registry;
@Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof ThemeElementCategory) {
String categoryId = ((ThemeElementCategory) parentElement)
.getId();
Object[] defintions = (Object[]) categoryMap.get(categoryId);
if (defintions == null) {
defintions = getCategoryChildren(categoryId);
categoryMap.put(categoryId, defintions);
}
return defintions;
}
ArrayList list = new ArrayList();
IHierarchalThemeElementDefinition def = (IHierarchalThemeElementDefinition) parentElement;
String id = def.getId();
IHierarchalThemeElementDefinition[] defs;
if (def instanceof ColorDefinition) {
defs = registry.getColors();
} else {
defs = registry.getFonts();
}
for (IHierarchalThemeElementDefinition elementDefinition : defs) {
if (id.equals(elementDefinition.getDefaultsTo())
&& ColorsAndFontsPreferencePage.equals(
((ICategorizedThemeElementDefinition) def).getCategoryId(),
((ICategorizedThemeElementDefinition) elementDefinition).getCategoryId())) {
list.add(elementDefinition);
}
}
return list.toArray();
}
private Object[] getCategoryChildren(String categoryId) {
ArrayList list = new ArrayList();
if (categoryId != null) {
for (ThemeElementCategory category : registry.getCategories()) {
if (categoryId.equals(category.getParentId())) {
Set bindings = themeRegistry.getPresentationsBindingsFor(category);
if (bindings == null) {
list.add(category);
}
}
}
}
{
ColorDefinition[] colorDefinitions = themeRegistry
.getColorsFor(currentTheme.getId());
for (int i = 0; i < colorDefinitions.length; i++) {
if (!colorDefinitions[i].isEditable()) {
continue;
}
String catId = colorDefinitions[i].getCategoryId();
if ((catId == null && categoryId == null)
|| (catId != null && categoryId != null && categoryId
.equals(catId))) {
if (colorDefinitions[i].getDefaultsTo() != null
&& parentIsInSameCategory(colorDefinitions[i])) {
continue;
}
list.add(colorDefinitions[i]);
}
}
}
{
FontDefinition[] fontDefinitions = themeRegistry
.getFontsFor(currentTheme.getId());
for (int i = 0; i < fontDefinitions.length; i++) {
if (!fontDefinitions[i].isEditable()) {
continue;
}
String catId = fontDefinitions[i].getCategoryId();
if ((catId == null && categoryId == null)
|| (catId != null && categoryId != null && categoryId
.equals(catId))) {
if (fontDefinitions[i].getDefaultsTo() != null
&& parentIsInSameCategory(fontDefinitions[i])) {
continue;
}
list.add(fontDefinitions[i]);
}
}
}
return list.toArray(new Object[list.size()]);
}
private boolean parentIsInSameCategory(ColorDefinition definition) {
String defaultsTo = definition.getDefaultsTo();
for (ColorDefinition colorDef : registry.getColors()) {
if (colorDef.getId().equals(defaultsTo) && ColorsAndFontsPreferencePage.equals(colorDef
.getCategoryId(), definition.getCategoryId())) {
return true;
}
}
return false;
}
private boolean parentIsInSameCategory(FontDefinition definition) {
String defaultsTo = definition.getDefaultsTo();
for (FontDefinition fontDef : registry.getFonts()) {
if (fontDef.getId().equals(defaultsTo) && ColorsAndFontsPreferencePage.equals(fontDef
.getCategoryId(), definition.getCategoryId())) {
return true;
}
}
return false;
}
@Override
public Object getParent(Object element) {
if (element instanceof ThemeElementCategory)
return registry;
if (element instanceof ColorDefinition) {
String defaultId = ((IHierarchalThemeElementDefinition) element).getDefaultsTo();
if (defaultId != null) {
ColorDefinition defaultElement = registry.findColor(defaultId);
if (parentIsInSameCategory(defaultElement))
return defaultElement;
}
String categoryId = ((ColorDefinition) element).getCategoryId();
return registry.findCategory(categoryId);
}
if (element instanceof FontDefinition) {
String defaultId = ((FontDefinition) element).getDefaultsTo();
if (defaultId != null) {
FontDefinition defaultElement = registry.findFont(defaultId);
if (parentIsInSameCategory(defaultElement))
return defaultElement;
}
String categoryId = ((FontDefinition) element).getCategoryId();
return registry.findCategory(categoryId);
}
return null;
}
@Override
public boolean hasChildren(Object element) {
if (element instanceof ThemeElementCategory) {
return true;
}
IHierarchalThemeElementDefinition def = (IHierarchalThemeElementDefinition) element;
String id = def.getId();
IHierarchalThemeElementDefinition[] defs;
if (def instanceof ColorDefinition) {
defs = registry.getColors();
} else {
defs = registry.getFonts();
}
for (IHierarchalThemeElementDefinition elementDefinition : defs) {
if (id.equals(elementDefinition.getDefaultsTo())
&& ColorsAndFontsPreferencePage.equals(
((ICategorizedThemeElementDefinition) def).getCategoryId(),
((ICategorizedThemeElementDefinition) elementDefinition).getCategoryId())) {
return true;
}
}
return false;
}
@Override
public Object[] getElements(Object inputElement) {
ArrayList list = new ArrayList();
Object[] uncatChildren = getCategoryChildren(null);
list.addAll(Arrays.asList(uncatChildren));
for (ThemeElementCategory category : ((IThemeRegistry) inputElement).getCategories()) {
if (category.getParentId() == null) {
Set bindings = themeRegistry.getPresentationsBindingsFor(category);
if (bindings == null) {
Object[] children = getChildren(category);
if (children != null && children.length > 0) {
list.add(category);
}
}
}
}
return list.toArray(new Object[list.size()]);
}
@Override
public void dispose() {
categoryMap.clear();
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
categoryMap.clear();
registry = (IThemeRegistry) newInput;
}
}
private class PresentationLabelProvider extends LabelProvider implements
IFontProvider,
IColorProvider {
private HashMap fonts = new HashMap();
private HashMap images = new HashMap();
private int imageSize = -1;
private int usableImageSize = -1;
private IPropertyChangeListener listener = event -> {
if (event.getNewValue() != null) {
fireLabelProviderChanged(new LabelProviderChangedEvent(
PresentationLabelProvider.this));
} else {
// Some theme definition element has been modified and we
// need to refresh the viewer
refreshAllLabels();
}
};
private Image emptyImage;
public PresentationLabelProvider() {
hookListeners();
}
/**
* Hook the listeners onto the various registries.
*/
public void hookListeners() {
colorRegistry.addListener(listener);
fontRegistry.addListener(listener);
}
@Override
public void dispose() {
super.dispose();
colorRegistry.removeListener(listener);
fontRegistry.removeListener(listener);
for (Iterator i = images.values().iterator(); i.hasNext();) {
((Image) i.next()).dispose();
}
images.clear();
if (emptyImage != null) {
emptyImage.dispose();
emptyImage = null;
}
//clear the fonts.
clearFontCache();
}
/**
* Clears and disposes all fonts.
*/
public void clearFontCache() {
for (Iterator i = fonts.values().iterator(); i.hasNext();) {
((Font) i.next()).dispose();
}
fonts.clear();
}
/**
* Clears and disposes all fonts and fires a label update.
*/
public void clearFontCacheAndUpdate() {
clearFontCache();
fireLabelProviderChanged(new LabelProviderChangedEvent(
PresentationLabelProvider.this));
}
@Override
public Font getFont(Object element) {
Display display = tree.getDisplay();
if (element instanceof FontDefinition) {
int parentHeight = tree.getViewer().getControl().getFont()
.getFontData()[0].getHeight();
Font baseFont = fontRegistry.get(((FontDefinition) element)
.getId());
Font font = (Font) fonts.get(baseFont);
if (font == null) {
FontData[] data = baseFont.getFontData();
for (FontData fontData : data) {
fontData.setHeight(parentHeight);
}
font = new Font(display, data);
fonts.put(baseFont, font);
}
return font;
}
return JFaceResources.getDialogFont();
}
@Override
public Image getImage(Object element) {
if (element instanceof ColorDefinition) {
Display display = tree.getDisplay();
Color c = colorRegistry
.get(((ColorDefinition) element).getId());
Color foregroundColor = display.getSystemColor(SWT.COLOR_BLACK);
if (!isAvailableInCurrentTheme((ColorDefinition) element)) {
c = display.getSystemColor(SWT.COLOR_WHITE);
foregroundColor = display.getSystemColor(DEFINITION_NOT_AVAIL_COLOR);
}
Image image = (Image) images.get(c);
if (image == null) {
ensureImageSize();
image = new Image(display, imageSize, imageSize);
GC gc = new GC(image);
gc.setBackground(tree.getViewer().getControl()
.getBackground());
gc.setForeground(tree.getViewer().getControl()
.getBackground());
gc.drawRectangle(0, 0, imageSize - 1, imageSize - 1);
gc.setForeground(foregroundColor);
gc.setBackground(c);
int offset = (imageSize - usableImageSize) / 2;
gc.drawRectangle(offset, offset, usableImageSize - offset,
usableImageSize - offset);
gc.fillRectangle(offset + 1, offset + 1, usableImageSize
- offset - 1, usableImageSize - offset - 1);
gc.dispose();
images.put(c, image);
}
return image;
} else if (element instanceof FontDefinition) {
return workbench.getSharedImages().getImage(
IWorkbenchGraphicConstants.IMG_OBJ_FONT);
} else {
return workbench.getSharedImages().getImage(
IWorkbenchGraphicConstants.IMG_OBJ_THEME_CATEGORY);
}
}
private void ensureImageSize() {
if (imageSize == -1) {
imageSize = tree.getViewer().getTree().getItemHeight();
usableImageSize = Math.max(1, imageSize - 4);
}
}
@Override
public String getText(Object element) {
if (element instanceof IHierarchalThemeElementDefinition) {
IHierarchalThemeElementDefinition themeElement = (IHierarchalThemeElementDefinition) element;
if (themeElement.getDefaultsTo() != null) {
String myCategory = ((ICategorizedThemeElementDefinition) themeElement).getCategoryId();
ICategorizedThemeElementDefinition def;
if (element instanceof ColorDefinition) {
def = themeRegistry.findColor(themeElement.getDefaultsTo());
} else {
def = themeRegistry.findFont(themeElement.getDefaultsTo());
}
if (def != null && !ColorsAndFontsPreferencePage.equals(def.getCategoryId(), myCategory)) {
if (isDefault(themeElement)) {
return MessageFormat.format(RESOURCE_BUNDLE.getString("defaultFormat_default"), //$NON-NLS-1$
themeElement.getName(), def.getName());
}
return MessageFormat.format(RESOURCE_BUNDLE.getString("defaultFormat_override"), //$NON-NLS-1$
themeElement.getName(), def.getName());
}
}
}
return ((IThemeElementDefinition) element).getName();
}
/**
* Return whether the element is set to default.
*
* @param def the definition
* @return whether the element is set to default
* @since 3.2
*/
private boolean isDefault(IThemeElementDefinition def) {
if (def instanceof FontDefinition) {
FontDefinition fontDef = (FontDefinition) def;
String defaultFontID = fontDef.getDefaultsTo();
return defaultFontID != null
&& Arrays.equals(fontRegistry.getFontData(def.getId()),
fontRegistry.getFontData(defaultFontID));
}
if (def instanceof ColorDefinition) {
ColorDefinition colorDef = (ColorDefinition) def;
String defaultColorID = colorDef.getDefaultsTo();
if (defaultColorID == null)
return false;
RGB defaultRGB = colorRegistry.getRGB(defaultColorID);
return defaultRGB != null
&& defaultRGB.equals(colorRegistry.getRGB(colorDef.getId()));
}
return false;
}
/**
* Returns the DEFINITION_NOT_AVAIL_COLOR color when definition is not
* present in the current theme or null when it is available
*
* @param def
* the definition
* @return the DEFINITION_NOT_AVAIL_COLOR color or null
*/
@Override
public Color getForeground(Object element) {
if (element instanceof ThemeElementDefinition && !isAvailableInCurrentTheme((ThemeElementDefinition) element)) {
return tree.getDisplay().getSystemColor(DEFINITION_NOT_AVAIL_COLOR);
}
return null;
}
@Override
public Color getBackground(Object element) {
return null;
}
}
/**
* The translation bundle in which to look up internationalized text.
*/
private final static ResourceBundle RESOURCE_BUNDLE = ResourceBundle
.getBundle(ColorsAndFontsPreferencePage.class.getName());
/**
* Map to precalculate category color lists.
*/
private Map categoryMap = new HashMap(7);
private Font appliedDialogFont;
/**
* Map of definition ColorDefinition->RGB capturing the explicit changes
* made by the user. These changes need to be stored into the preference
* store.
*/
private Map<ColorDefinition, RGB> colorPreferencesToSet = new HashMap<>(7);
private CascadingColorRegistry colorRegistry;
/**
* Map of definition id->RGB capturing the temporary changes caused by a
* 'defaultsTo' color change.
*/
private Map colorValuesToSet = new HashMap(7);
/**
* The default color preview composite.
*/
private Composite defaultColorPreview;
/**
* The default font preview composite.
*/
private Composite defaultFontPreview;
/**
* The composite to use when no preview is available.
*/
private Composite defaultNoPreview;
/**
* Currently selected font for preview; might be null.
*/
private Font currentFont;
/**
* Currently selected color for preview; might be null.
*/
private Color currentColor;
/**
* Canvas used to draw default color preview
*/
private Canvas colorSampler;
/**
* Canvas used to draw default font preview
*/
private Canvas fontSampler;
private String fontSampleText;
private List dialogFontWidgets = new ArrayList();
private Button fontChangeButton;
/**
* The button to edit the default of the selected element.
*
* @since 3.7
*/
private Button editDefaultButton;
/**
* The button to go to the default of the selected element.
*
* @since 3.7
*/
private Button goToDefaultButton;
/**
* The button to expand the tree.
*
* @since 4.5
*/
private Button expandAllButton;
/**
* Map of definition FontDefinition->FontData[] capturing the changes
* explicitly made by the user. These changes need to be stored into the
* preference store.
*/
private Map<FontDefinition, FontData[]> fontPreferencesToSet = new HashMap<>(
7);
private CascadingFontRegistry fontRegistry;
private Button fontResetButton;
private Button fontSystemButton;
/**
* Map of definition id->FontData[] capturing the temporary changes caused
* by a 'defaultsTo' font change.
*
* @deprecated in this page, we should only care about preferences,
* preference to fontValues synchronization in registry is
* handled in the ThemeAPI and listeners
*/
@Deprecated
private Map fontValuesToSet = new HashMap(7);
/**
* The composite that is parent to all previews.
*/
private Composite previewComposite;
/**
* A mapping from PresentationCategory->Composite for the created previews.
*/
private Map previewMap = new HashMap(7);
/**
* Set containing all IPresentationPreviews created.
*/
private Set previewSet = new HashSet(7);
/**
* The layout for the previewComposite.
*/
private StackLayout stackLayout;
private final IThemeRegistry themeRegistry;
private ITheme currentTheme;
private PresentationLabelProvider labelProvider;
private CascadingThemeExt cascadingTheme;
private IPropertyChangeListener themeChangeListener;
private Workbench workbench;
private FilteredTree tree;
private Text descriptionText;
private IEventBroker eventBroker;
private EventHandler themeRegistryRestyledHandler = new EventHandler() {
@Override
public void handleEvent(Event event) {
if (isAnyThemeChanged()) {
updateThemeInfo(workbench.getThemeManager());
}
refreshCategory();
refreshAllLabels();
clearPreviews();
previewMap.clear();
restoreTreeSelection();
updateControls();
}
private boolean isAnyThemeChanged() {
return currentTheme != workbench.getThemeManager().getCurrentTheme()
|| currentCSSTheme != getActiveTheme();
}
};
private org.eclipse.e4.ui.css.swt.theme.ITheme currentCSSTheme;
private IThemeEngine themeEngine;
/**
* Create a new instance of the receiver.
*/
public ColorsAndFontsPreferencePage() {
themeRegistry = WorkbenchPlugin.getDefault().getThemeRegistry();
//no-op
}
/**
* {@inheritDoc}
* <p>
* Everything else except the following string patterns is ignored:
* <ul>
* <li><strong>selectCategory:</strong>ID - selects and expands the category
* with the given ID</li>
* <li><strong>selectFont:</strong>ID - selects the font with the given ID</li>
* <li><strong>selectColor:</strong>ID - selects the color with the given ID
* </li>
* </p>
*
* @param data
* the data to be applied
*/
@Override
public void applyData(Object data) {
if (tree == null || !(data instanceof String))
return;
ThemeRegistry themeRegistry = (ThemeRegistry) tree.getViewer().getInput();
String command = (String) data;
if (command.startsWith("selectCategory:")) { //$NON-NLS-1$
String categoryId = command.substring(15);
ThemeElementCategory category = themeRegistry.findCategory(categoryId);
if (category != null) {
selectAndReveal(category);
tree.getViewer().expandToLevel(category, 1);
}
} else if (command.startsWith("selectFont:")) { //$NON-NLS-1$
String id = command.substring(11);
FontDefinition fontDef = themeRegistry.findFont(id);
if (fontDef != null) {
selectAndReveal(fontDef);
}
} else if (command.startsWith("selectColor:")) { //$NON-NLS-1$
String id = command.substring(12);
ColorDefinition colorDef = themeRegistry.findColor(id);
if (colorDef != null) {
selectAndReveal(colorDef);
}
}
}
/**
* Selects and reveals the given element.
*
* @param selection
* the object to select and reveal
* @since 3.7
*/
private void selectAndReveal(Object selection) {
TreeViewer viewer = tree.getViewer();
viewer.setSelection(new StructuredSelection(selection), false);
viewer.reveal(selection);
viewer.getTree().setFocus();
}
private static boolean equals(String string, String string2) {
if ((string == null && string2 == null))
return true;
if (string == null || string2 == null)
return false;
if (string.equals(string2))
return true;
return false;
}
/**
* Get the active theme.
*
* @return the active theme if there is one; <code>null</code> if there
* isn't or {@link #themeEngine} is <code>null</code>.
*/
private org.eclipse.e4.ui.css.swt.theme.ITheme getActiveTheme() {
if (themeEngine != null) {
return themeEngine.getActiveTheme();
}
return null;
}
/**
* Create a button for the preference page.
* @param parent
* @param label
*/
private Button createButton(Composite parent, String label) {
Button button = new Button(parent, SWT.PUSH | SWT.CENTER);
button.setText(label);
myApplyDialogFont(button);
setButtonLayoutData(button);
button.setEnabled(false);
return button;
}
private Label createSeparator(Composite parent) {
Label separator = new Label(parent, SWT.NONE);
separator.setFont(parent.getFont());
separator.setVisible(false);
GridData gd = new GridData();
gd.horizontalAlignment = GridData.FILL;
gd.verticalAlignment = GridData.BEGINNING;
gd.heightHint = 4;
separator.setLayoutData(gd);
return separator;
}
@Override
protected Control createContents(Composite parent) {
PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, IWorkbenchHelpContextIds.FONTS_PREFERENCE_PAGE);
parent.addDisposeListener(e -> {
if (appliedDialogFont != null)
appliedDialogFont.dispose();
});
final SashForm advancedComposite = new SashForm(parent, SWT.VERTICAL);
GridData sashData = new GridData(SWT.FILL, SWT.FILL, true, true);
advancedComposite.setLayoutData(sashData);
Composite mainColumn = new Composite(advancedComposite, SWT.NONE);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
layout.marginWidth = 0;
layout.marginHeight = 0;
mainColumn.setFont(parent.getFont());
mainColumn.setLayout(layout);
GridData data = new GridData(GridData.BEGINNING);
data.horizontalSpan = 2;
Label label = new Label(mainColumn, SWT.LEFT);
label.setText(RESOURCE_BUNDLE.getString("colorsAndFonts")); //$NON-NLS-1$
myApplyDialogFont(label);
label.setLayoutData(data);
createTree(mainColumn);
// --- buttons
Composite controlColumn = new Composite(mainColumn, SWT.NONE);
data = new GridData(GridData.FILL_VERTICAL);
controlColumn.setLayoutData(data);
layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
controlColumn.setLayout(layout);
// we need placeholder to offset the filter control of the table
Label placeholder = new Label(controlColumn, SWT.NONE);
GridData placeholderData = new GridData(SWT.TOP);
placeholderData.heightHint = convertVerticalDLUsToPixels(12);
placeholder.setLayoutData(placeholderData);
fontChangeButton = createButton(controlColumn, RESOURCE_BUNDLE.getString("openChange")); //$NON-NLS-1$
fontSystemButton = createButton(controlColumn, WorkbenchMessages.FontsPreference_useSystemFont);
fontResetButton = createButton(controlColumn, RESOURCE_BUNDLE.getString("reset")); //$NON-NLS-1$
createSeparator(controlColumn);
editDefaultButton = createButton(controlColumn, RESOURCE_BUNDLE.getString("editDefault")); //$NON-NLS-1$
goToDefaultButton = createButton(controlColumn, RESOURCE_BUNDLE.getString("goToDefault")); //$NON-NLS-1$
createSeparator(controlColumn);
expandAllButton = createButton(controlColumn, RESOURCE_BUNDLE.getString("expandAll")); //$NON-NLS-1$
expandAllButton.setEnabled(true);
// --- end of buttons
createDescriptionControl(mainColumn);
Composite previewColumn = new Composite(advancedComposite, SWT.NONE);
GridLayout previewLayout = new GridLayout();
previewLayout.marginTop = 7;
previewLayout.marginWidth = 0;
previewLayout.marginHeight = 0;
previewColumn.setFont(parent.getFont());
previewColumn.setLayout(previewLayout);
// --- create preview control
Composite composite = new Composite(previewColumn, SWT.NONE);
GridData data2 = new GridData(GridData.FILL_BOTH);
composite.setLayoutData(data2);
GridLayout layout2 = new GridLayout(1, true);
layout2.marginHeight = 0;
layout2.marginWidth = 0;
composite.setLayout(layout2);
Label label2 = new Label(composite, SWT.LEFT);
label2.setText(RESOURCE_BUNDLE.getString("preview")); //$NON-NLS-1$
myApplyDialogFont(label2);
previewComposite = new Composite(composite, SWT.NONE);
previewComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
stackLayout = new StackLayout();
stackLayout.marginHeight = 0;
stackLayout.marginWidth = 0;
previewComposite.setLayout(stackLayout);
// -- end of preview control
defaultFontPreview = createFontPreviewControl();
defaultColorPreview = createColorPreviewControl();
defaultNoPreview = createNoPreviewControl();
hookListeners();
updateTreeSelection(tree.getViewer().getSelection());
advancedComposite.setWeights(new int[] { 75, 25 });
return advancedComposite;
}
/**
* Create the <code>ListViewer</code> that will contain all color
* definitions as defined in the extension point.
*
* @param parent
* the parent <code>Composite</code>.
*/
private void createTree(Composite parent) {
labelProvider = new PresentationLabelProvider();
// Create a custom pattern matcher that will allow
// non-category elements to be returned in the event that their children
// do not and also search the descriptions.
PatternFilter filter = new PatternFilter() {
@Override
protected boolean isLeafMatch(Viewer viewer, Object element) {
if (super.isLeafMatch(viewer, element))
return true;
String text = null;
if (element instanceof ICategorizedThemeElementDefinition)
text = ((ICategorizedThemeElementDefinition) element).getDescription();
return text != null ? wordMatches(text) : false;
}
};
filter.setIncludeLeadingWildcard(true);
tree = new FilteredTree(parent, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER,
filter, true);
tree.setQuickSelectionMode(true);
GridData data = new GridData(GridData.FILL_BOTH | GridData.VERTICAL_ALIGN_FILL);
data.widthHint = Math.max(285, convertWidthInCharsToPixels(30));
data.heightHint = Math.max(175, convertHeightInCharsToPixels(10));
tree.setLayoutData(data);
myApplyDialogFont(tree.getViewer().getControl());
Text filterText = tree.getFilterControl();
if (filterText != null)
myApplyDialogFont(filterText);
tree.getViewer().setLabelProvider(labelProvider);
tree.getViewer().setContentProvider(new ThemeContentProvider());
tree.getViewer().setComparator(new ViewerComparator() {
@Override
public int category(Object element) {
if (element instanceof ThemeElementCategory)
return 0;
return 1;
}
});
tree.getViewer().setInput(WorkbenchPlugin.getDefault().getThemeRegistry());
tree.getViewer().addDoubleClickListener(event -> {
IStructuredSelection s = (IStructuredSelection) event.getSelection();
Object element = s.getFirstElement();
if (tree.getViewer().isExpandable(element))
tree.getViewer().setExpandedState(element,
!tree.getViewer().getExpandedState(element));
if (element instanceof ThemeElementDefinition) {
ThemeElementDefinition definition = (ThemeElementDefinition) element;
if (element instanceof FontDefinition) {
editFont(tree.getDisplay());
} else if (element instanceof ColorDefinition
&& isAvailableInCurrentTheme(definition)) {
editColor(tree.getDisplay());
}
updateControls();
}
});
restoreTreeExpansion();
restoreTreeSelection();
}
@Override
public void dispose() {
eventBroker.unsubscribe(themeRegistryRestyledHandler);
workbench.getThemeManager().removePropertyChangeListener(themeChangeListener);
clearPreviews();
colorRegistry.dispose();
fontRegistry.dispose();
super.dispose();
}
/**
* Clear all previews.
*/
private void clearPreviews() {
if (cascadingTheme != null)
cascadingTheme.dispose();
for (Iterator i = previewSet.iterator(); i.hasNext();) {
IThemePreview preview = (IThemePreview) i.next();
try {
preview.dispose();
} catch (RuntimeException e) {
WorkbenchPlugin.log(RESOURCE_BUNDLE.getString("errorDisposePreviewLog"), //$NON-NLS-1$
StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e));
}
}
previewSet.clear();
}
/**
* Get the ancestor of the given color, if any.
*
* @param definition the descendant <code>ColorDefinition</code>.
* @return the ancestor <code>ColorDefinition</code>, or <code>null</code>
* if none.
*/
private ColorDefinition getColorAncestor(ColorDefinition definition) {
String defaultsTo = definition.getDefaultsTo();
if (defaultsTo == null)
return null;
return themeRegistry.findColor(defaultsTo);
}
/**
* Get the RGB value of the given colors ancestor, if any.
*
* @param definition the descendant <code>ColorDefinition</code>.
* @return the ancestor <code>RGB</code>, or <code>null</code> if none.
*/
private RGB getColorAncestorValue(ColorDefinition definition) {
ColorDefinition ancestor = getColorAncestor(definition);
if (ancestor == null)
return null;
return getColorValue(ancestor);
}
/**
* Get the RGB value for the specified definition. Cascades through
* preferenceToSet, valuesToSet and finally the registry.
*
* @param definition the <code>ColorDefinition</code>.
* @return the <code>RGB</code> value.
*/
private RGB getColorValue(ColorDefinition definition) {
String id = definition.getId();
RGB updatedRGB = colorPreferencesToSet.get(definition);
if (updatedRGB == null) {
updatedRGB = (RGB) colorValuesToSet.get(id);
if (updatedRGB == null)
updatedRGB = currentTheme.getColorRegistry().getRGB(id);
}
return updatedRGB;
}
/**
* Get colors that descend from the provided color.
*
* @param definition the ancestor <code>ColorDefinition</code>.
* @return the ColorDefinitions that have the provided definition as their
* defaultsTo attribute.
*/
private ColorDefinition[] getDescendantColors(ColorDefinition definition) {
List list = new ArrayList(5);
String id = definition.getId();
ColorDefinition[] colors = themeRegistry.getColors();
ColorDefinition[] sorted = new ColorDefinition[colors.length];
System.arraycopy(colors, 0, sorted, 0, sorted.length);
Arrays.sort(sorted, new IThemeRegistry.HierarchyComparator(colors));
for (ColorDefinition colorDefinition : sorted) {
if (id.equals(colorDefinition.getDefaultsTo()))
list.add(colorDefinition);
}
return (ColorDefinition[]) list.toArray(new ColorDefinition[list.size()]);
}
private FontDefinition[] getDescendantFonts(FontDefinition definition) {
List list = new ArrayList(5);
String id = definition.getId();
FontDefinition[] fonts = themeRegistry.getFonts();
FontDefinition[] sorted = new FontDefinition[fonts.length];
System.arraycopy(fonts, 0, sorted, 0, sorted.length);
Arrays.sort(sorted, new IThemeRegistry.HierarchyComparator(fonts));
for (FontDefinition fontDefinition : sorted) {
if (id.equals(fontDefinition.getDefaultsTo()))
list.add(fontDefinition);
}
return (FontDefinition[]) list.toArray(new FontDefinition[list.size()]);
}
private FontDefinition getFontAncestor(FontDefinition definition) {
String defaultsTo = definition.getDefaultsTo();
if (defaultsTo == null)
return null;
return themeRegistry.findFont(defaultsTo);
}
private FontData[] getFontAncestorValue(FontDefinition definition) {
FontDefinition ancestor = getFontAncestor(definition);
if (ancestor == null) {
return PreferenceConverter.getDefaultFontDataArray(
getPreferenceStore(),
createPreferenceKey(definition));
}
return getFontValue(ancestor);
}
protected FontData[] getFontValue(FontDefinition definition) {
String id = definition.getId();
FontData[] updatedFD = fontPreferencesToSet.get(definition);
if (updatedFD == null) {
updatedFD = (FontData[]) fontValuesToSet.get(id);
if (updatedFD == null)
updatedFD = currentTheme.getFontRegistry().getFontData(id);
}
return updatedFD;
}
protected ColorDefinition getSelectedColorDefinition() {
Object o = tree.getViewer().getStructuredSelection().getFirstElement();
if (o instanceof ColorDefinition) {
return themeRegistry.findColor(((ColorDefinition) o).getId());
}
return null;
}
protected FontDefinition getSelectedFontDefinition() {
Object o = tree.getViewer().getStructuredSelection().getFirstElement();
if (o instanceof FontDefinition) {
return themeRegistry.findFont(((FontDefinition) o).getId());
}
return null;
}
protected boolean isFontSelected() {
Object o = tree.getViewer().getStructuredSelection().getFirstElement();
return (o instanceof FontDefinition);
}
protected boolean isColorSelected() {
Object o = tree.getViewer().getStructuredSelection().getFirstElement();
return (o instanceof ColorDefinition);
}
/**
* Hook all control listeners.
*/
private void hookListeners() {
TreeViewer viewer = tree.getViewer();
viewer.addSelectionChangedListener(event -> updateTreeSelection(event.getSelection()));
fontChangeButton.addSelectionListener(widgetSelectedAdapter(event -> {
Display display = event.display;
if (isFontSelected())
editFont(display);
else if (isColorSelected())
editColor(display);
updateControls();
}));
fontResetButton.addSelectionListener(widgetSelectedAdapter(e -> {
if (isFontSelected())
resetFont(getSelectedFontDefinition(), false);
else if (isColorSelected())
resetColor(getSelectedColorDefinition(), false);
updateControls();
}));
fontSystemButton.addSelectionListener(widgetSelectedAdapter(event -> {
FontDefinition definition = getSelectedFontDefinition();
if (definition == null)
return;
FontData[] defaultFontData = JFaceResources.getDefaultFont().getFontData();
setFontPreferenceValue(definition, defaultFontData, false);
updateControls();
}));
editDefaultButton.addSelectionListener(widgetSelectedAdapter(event -> {
Display display = event.display;
FontDefinition fontDefinition = getSelectedFontDefinition();
if (fontDefinition != null) {
String defaultFontId = fontDefinition.getDefaultsTo();
FontDefinition defaultFontDefinition = themeRegistry.findFont(defaultFontId);
editFont(defaultFontDefinition, display);
} else {
ColorDefinition colorDefinition = getSelectedColorDefinition();
if (colorDefinition != null) {
String defaultColorId = colorDefinition.getDefaultsTo();
ColorDefinition defaultColorDefinition = themeRegistry
.findColor(defaultColorId);
editColor(defaultColorDefinition, display);
}
}
updateControls();
}));
goToDefaultButton.addSelectionListener(widgetSelectedAdapter(event -> {
FontDefinition fontDefinition = getSelectedFontDefinition();
if (fontDefinition != null) {
String defaultFontId = fontDefinition.getDefaultsTo();
FontDefinition defaultFontDefinition = themeRegistry.findFont(defaultFontId);
selectAndReveal(defaultFontDefinition);
} else {
ColorDefinition colorDefinition = getSelectedColorDefinition();
if (colorDefinition != null) {
String defaultColorId = colorDefinition.getDefaultsTo();
ColorDefinition defaultColorDefinition = themeRegistry
.findColor(defaultColorId);
selectAndReveal(defaultColorDefinition);
}
}
updateControls();
}));
expandAllButton.addSelectionListener(widgetSelectedAdapter(event -> tree.getViewer().expandAll()));
}
@Override
public void init(IWorkbench aWorkbench) {
this.workbench = (Workbench) aWorkbench;
themeEngine = workbench.getService(IThemeEngine.class);
setPreferenceStore(PrefUtil.getInternalPreferenceStore());
final IThemeManager themeManager = aWorkbench.getThemeManager();
themeChangeListener = event -> {
if (event.getProperty().equals(
IThemeManager.CHANGE_CURRENT_THEME)) {
updateThemeInfo(themeManager);
refreshCategory();
refreshAllLabels();
}
};
themeManager.addPropertyChangeListener(themeChangeListener);
updateThemeInfo(themeManager);
eventBroker = workbench.getService(IEventBroker.class);
eventBroker.subscribe(WorkbenchThemeManager.Events.THEME_REGISTRY_RESTYLED,
themeRegistryRestyledHandler);
}
private void updateThemeInfo(IThemeManager manager) {
clearPreviews();
categoryMap.clear();
if (labelProvider != null)
labelProvider.dispose(); // nuke the old cache
if (colorRegistry != null)
colorRegistry.dispose();
if (fontRegistry != null)
fontRegistry.dispose();
currentTheme = manager.getCurrentTheme();
currentCSSTheme = getActiveTheme();
colorRegistry = new CascadingColorRegistry(currentTheme.getColorRegistry());
fontRegistry = new CascadingFontRegistry(currentTheme.getFontRegistry());
if (cascadingTheme != null) {
cascadingTheme.dispose();
cascadingTheme = new CascadingThemeExt(currentTheme, colorRegistry, fontRegistry);
}
fontPreferencesToSet.clear();
fontValuesToSet.clear();
colorPreferencesToSet.clear();
colorValuesToSet.clear();
if (labelProvider != null)
labelProvider.hookListeners(); // rehook the listeners
}
/**
* Answers whether the definition is currently set to the default value.
*
* @param definition the <code>ColorDefinition</code> to check.
* @return Return whether the definition is currently mapped to the default
* value, either in the preference store or in the local change record
* of this preference page.
*/
private boolean isDefault(ColorDefinition definition) {
String id = definition.getId();
if (colorPreferencesToSet.containsKey(definition)) {
if (definition.getValue() != null) { // value-based color
if (colorPreferencesToSet.get(definition).equals(definition.getValue()))
return true;
} else {
if (colorPreferencesToSet.get(definition).equals(getColorAncestorValue(definition)))
return true;
}
} else if (colorValuesToSet.containsKey(id)) {
if (definition.getValue() != null) { // value-based color
if (colorValuesToSet.get(id).equals(definition.getValue()))
return true;
} else {
if (colorValuesToSet.get(id).equals(getColorAncestorValue(definition)))
return true;
}
} else {
if (definition.getValue() != null) { // value-based color
if (getPreferenceStore().isDefault(createPreferenceKey(definition)))
return true;
} else {
// a descendant is default if it's the same value as its ancestor
RGB rgb = getColorValue(definition);
if (rgb != null && rgb.equals(getColorAncestorValue(definition)))
return true;
}
}
return false;
}
private boolean isDefault(FontDefinition definition) {
String id = definition.getId();
if (fontPreferencesToSet.containsKey(definition)) {
if (definition.getValue() != null) { // value-based font
if (Arrays.equals(fontPreferencesToSet.get(definition),
definition.getValue()))
return true;
} else {
FontData[] ancestor = getFontAncestorValue(definition);
if (Arrays.equals(fontPreferencesToSet.get(definition), ancestor))
return true;
}
} else if (fontValuesToSet.containsKey(id)) {
if (definition.getValue() != null) { // value-based font
if (Arrays.equals((FontData[]) fontValuesToSet.get(id), definition.getValue()))
return true;
} else {
FontData[] ancestor = getFontAncestorValue(definition);
if (Arrays.equals((FontData[]) fontValuesToSet.get(id), ancestor))
return true;
}
} else {
if (definition.getValue() != null) { // value-based font
if (getPreferenceStore().isDefault(createPreferenceKey(definition)))
return true;
} else {
FontData[] ancestor = getFontAncestorValue(definition);
if (ancestor == null)
return true;
// a descendant is default if it's the same value as its ancestor
if (Arrays.equals(getFontValue(definition), ancestor))
return true;
}
}
return false;
}
/**
* Apply the dialog font to the control and store
* it for later so that it can be used for a later
* update.
* @param control
*/
private void myApplyDialogFont(Control control) {
control.setFont(JFaceResources.getDialogFont());
dialogFontWidgets.add(control);
}
/**
* @see org.eclipse.jface.preference.PreferencePage#performApply()
*/
@Override
protected void performApply() {
super.performApply();
//Apply the default font to the dialog.
Font oldFont = appliedDialogFont;
FontDefinition fontDefinition = themeRegistry.findFont(JFaceResources.DIALOG_FONT);
if (fontDefinition == null)
return;
FontData[] newData = getFontValue(fontDefinition);
appliedDialogFont = new Font(getControl().getDisplay(), newData);
updateForDialogFontChange(appliedDialogFont);
getApplyButton().setFont(appliedDialogFont);
getDefaultsButton().setFont(appliedDialogFont);
if (oldFont != null) {
oldFont.dispose();
}
if (performColorOk() && performFontOk()) {
publishThemeRegistryModifiedEvent();
}
}
private void performColorDefaults() {
ColorDefinition[] definitions = themeRegistry.getColors();
// apply defaults in depth-order.
ColorDefinition[] definitionsCopy = new ColorDefinition[definitions.length];
System.arraycopy(definitions, 0, definitionsCopy, 0,definitions.length);
Arrays.sort(definitionsCopy, new IThemeRegistry.HierarchyComparator(definitions));
for (ColorDefinition colorDefinition : definitionsCopy) {
resetColor(colorDefinition, true);
}
}
private boolean performColorOk() {
for (Map.Entry<ColorDefinition, RGB> entry : colorPreferencesToSet.entrySet()) {
String key = createPreferenceKey(entry.getKey());
String rgbString = StringConverter.asString(entry.getValue());
String storeString = getPreferenceStore().getString(key);
if (!rgbString.equals(storeString))
getPreferenceStore().setValue(key, rgbString);
}
colorValuesToSet.clear();
colorPreferencesToSet.clear();
return true;
}
private String createPreferenceKey(ThemeElementDefinition definition) {
if (definition.isOverridden() || definition.isAddedByCss()) {
return ThemeElementHelper.createPreferenceKey(currentCSSTheme, currentTheme,
definition.getId());
}
return ThemeElementHelper.createPreferenceKey(currentTheme, definition.getId());
}
@Override
public void performDefaults() {
performColorDefaults();
performFontDefaults();
updateControls();
tree.getViewer().refresh();
}
private void performFontDefaults() {
FontDefinition[] definitions = themeRegistry.getFonts();
// apply defaults in depth-order.
FontDefinition[] definitionsCopy = new FontDefinition[definitions.length];
System.arraycopy(definitions, 0, definitionsCopy, 0, definitions.length);
Arrays.sort(definitionsCopy, new IThemeRegistry.HierarchyComparator(definitions));
for (FontDefinition fontDefinition : definitionsCopy) {
resetFont(fontDefinition, true);
}
}
private boolean performFontOk() {
for (Map.Entry<FontDefinition, FontData[]> entry : fontPreferencesToSet.entrySet()) {
String key = createPreferenceKey(entry.getKey());
String fdString = PreferenceConverter.getStoredRepresentation(entry.getValue());
String storeString = getPreferenceStore().getString(key);
if (!fdString.equals(storeString))
getPreferenceStore().setValue(key, fdString);
}
fontValuesToSet.clear();
fontPreferencesToSet.clear();
return true;
}
@Override
public boolean performOk() {
saveTreeExpansion();
saveTreeSelection();
boolean result = performColorOk() && performFontOk();
if (result) {
PrefUtil.savePrefs();
publishThemeRegistryModifiedEvent();
}
return result;
}
/**
* Refreshes the category.
*/
private void refreshCategory() {
updateControls();
}
/**
* Resets the supplied definition to its default value.
*
* @param definition the <code>ColorDefinition</code> to reset.
* @return whether any change was made.
*/
private boolean resetColor(ColorDefinition definition, boolean force) {
if (force || !isDefault(definition)) {
RGB newRGB;
if (definition.getValue() != null)
newRGB = definition.getValue();
else
newRGB = getColorAncestorValue(definition);
if (newRGB != null) {
setColorPreferenceValue(definition, newRGB, true);
setRegistryValue(definition, newRGB);
refreshElement(definition);
return true;
}
}
return false;
}
protected boolean resetFont(FontDefinition definition, boolean force) {
if (force || !isDefault(definition)) {
FontData[] newFD;
if (definition.isOverridden()) {
newFD = definition.getValue();
} else if (!force && definition.getDefaultsTo() != null) {
newFD = getFontAncestorValue(definition);
} else {
newFD = PreferenceConverter.getDefaultFontDataArray(getPreferenceStore(),
createPreferenceKey(definition));
}
if (newFD != null) {
setFontPreferenceValue(definition, newFD, true);
refreshElement(definition);
return true;
}
}
return false;
}
/**
* Set the value (in preferences) for the given color.
*
* @param definition the <code>ColorDefinition</code> to set.
* @param newRGB the new <code>RGB</code> value for the definitions
* identifier.
*/
protected void setColorPreferenceValue(ColorDefinition definition, RGB newRGB, boolean reset) {
setDescendantRegistryValues(definition, newRGB);
colorPreferencesToSet.put(definition, newRGB);
updateDefinitionState(definition, reset);
}
/**
* Set the value (in registry) for the given colors children.
*
* @param definition the <code>ColorDefinition</code> whose children should
* be set.
* @param newRGB the new <code>RGB</code> value for the definitions
* identifier.
*/
private void setDescendantRegistryValues(ColorDefinition definition, RGB newRGB) {
for (ColorDefinition colorDefinition : getDescendantColors(definition)) {
if (isDefault(colorDefinition)) {
setDescendantRegistryValues(colorDefinition, newRGB);
setRegistryValue(colorDefinition, newRGB);
colorValuesToSet.put(colorDefinition.getId(), newRGB);
}
}
}
private void setDescendantRegistryValues(FontDefinition definition, FontData[] datas, boolean reset) {
for (FontDefinition fontDefinition : getDescendantFonts(definition)) {
if (isDefault(fontDefinition)) {
setFontPreferenceValue(fontDefinition, datas, reset);
}
}
}
protected void setFontPreferenceValue(FontDefinition definition, FontData[] datas, boolean reset) {
// descendant values must be computed and set before updating current,
// or isDefault will miss them
setDescendantRegistryValues(definition, datas, reset);
fontPreferencesToSet.put(definition, datas);
setRegistryValue(definition, datas);
updateDefinitionState(definition, reset);
}
/**
* Updates the working registry.
* @param definition
* @param newRGB
*/
protected void setRegistryValue(ColorDefinition definition, RGB newRGB) {
colorRegistry.put(definition.getId(), newRGB);
}
protected void setRegistryValue(FontDefinition definition, FontData[] datas) {
fontRegistry.put(definition.getId(), datas);
}
/**
* Returns the preview for the category.
* @param category the category
* @return the preview for the category, or its ancestors preview if it does not have one.
*/
private IThemePreview getThemePreview(ThemeElementCategory category) throws CoreException {
IThemePreview preview = category.createPreview();
if (preview != null)
return preview;
if (category.getParentId() != null) {
int idx = Arrays.binarySearch(themeRegistry.getCategories(),
category.getParentId(), IThemeRegistry.ID_COMPARATOR);
if (idx >= 0)
return getThemePreview(themeRegistry.getCategories()[idx]);
}
return null;
}
private CascadingThemeExt getCascadingTheme() {
if (cascadingTheme == null) {
cascadingTheme = new CascadingThemeExt(currentTheme, colorRegistry, fontRegistry);
}
return cascadingTheme;
}
/**
* Update for a change in the dialog font.
* @param newFont
*/
private void updateForDialogFontChange(Font newFont) {
Iterator iterator = dialogFontWidgets.iterator();
while (iterator.hasNext()) {
((Control) iterator.next()).setFont(newFont);
}
//recalculate the fonts for the tree
labelProvider.clearFontCacheAndUpdate();
}
private void updateTreeSelection(ISelection selection) {
ThemeElementCategory category = null;
Object element = ((IStructuredSelection) selection).getFirstElement();
if (element instanceof ThemeElementCategory) {
category = (ThemeElementCategory) element;
} else if (element instanceof ColorDefinition) {
String categoryID = ((ColorDefinition) element).getCategoryId();
category = WorkbenchPlugin.getDefault().getThemeRegistry().findCategory(categoryID);
} else if (element instanceof FontDefinition) {
String categoryID = ((FontDefinition) element).getCategoryId();
category = WorkbenchPlugin.getDefault().getThemeRegistry().findCategory(categoryID);
}
Composite previewControl = null;
if (category != null) { // check if there is a preview for it
previewControl = (Composite) previewMap.get(category);
if (previewControl == null) {
try {
IThemePreview preview = getThemePreview(category);
if (preview != null) {
previewControl = new Composite(previewComposite, SWT.NONE);
previewControl.setLayout(new FillLayout());
ITheme theme = getCascadingTheme();
preview.createControl(previewControl, theme);
previewSet.add(preview);
previewMap.put(category, previewControl);
}
} catch (CoreException e) {
previewControl = new Composite(previewComposite, SWT.NONE);
previewControl.setLayout(new FillLayout());
myApplyDialogFont(previewControl);
Text error = new Text(previewControl, SWT.WRAP | SWT.READ_ONLY);
error.setText(RESOURCE_BUNDLE.getString("errorCreatingPreview")); //$NON-NLS-1$
WorkbenchPlugin.log(RESOURCE_BUNDLE.getString("errorCreatePreviewLog"), //$NON-NLS-1$
StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e));
}
}
}
if (previewControl == null) { // there is no preview for this theme, use default preview
if (element instanceof ColorDefinition)
previewControl = defaultColorPreview;
else if (element instanceof FontDefinition)
previewControl = defaultFontPreview;
else
previewControl = defaultNoPreview;
}
stackLayout.topControl = previewControl;
previewComposite.layout();
updateControls();
}
/**
* Restore the selection state of the tree.
* @since 3.1
*/
private void restoreTreeSelection() {
String selectedElementString = getPreferenceStore().getString(SELECTED_ELEMENT_PREF);
if (selectedElementString == null)
return;
Object element = findElementFromMarker(selectedElementString);
if (element == null)
return;
tree.getViewer().setSelection(new StructuredSelection(element), true);
}
/**
* Save the selection state of the tree.
* @since 3.1
*/
private void saveTreeSelection() {
IStructuredSelection selection = tree.getViewer().getStructuredSelection();
Object element = selection.getFirstElement();
StringBuffer buffer = new StringBuffer();
appendMarkerToBuffer(buffer, element);
if (buffer.length() > 0)
buffer.append(((IThemeElementDefinition) element).getId());
getPreferenceStore().setValue(SELECTED_ELEMENT_PREF, buffer.toString());
}
/**
* Restore the expansion state of the tree.
* @since 3.1
*/
private void restoreTreeExpansion() {
String expandedElementsString = getPreferenceStore().getString(EXPANDED_ELEMENTS_PREF);
if (expandedElementsString == null)
return;
String[] expandedElementIDs = expandedElementsString.split(EXPANDED_ELEMENTS_TOKEN);
if (expandedElementIDs.length == 0)
return;
List elements = new ArrayList(expandedElementIDs.length);
for (String expandedElementID : expandedElementIDs) {
IThemeElementDefinition def = findElementFromMarker(expandedElementID);
if (def != null)
elements.add(def);
}
tree.getViewer().setExpandedElements(elements.toArray());
}
/**
* Find the theme element from the given string. It will check the first
* character against the known constants and then call the appropriate
* method on the theme registry. If the element does not exist or the string
* is invalid <code>null</code> is returned.
*
* @param string the string to parse
* @return the element, or <code>null</code>
*/
private IThemeElementDefinition findElementFromMarker(String string) {
if (string.length() < 2)
return null;
char marker = string.charAt(0);
String id = string.substring(1);
IThemeElementDefinition def = null;
switch (marker) {
case MARKER_FONT:
def = themeRegistry.findFont(id);
break;
case MARKER_COLOR:
def = themeRegistry.findColor(id);
break;
case MARKER_CATEGORY:
def = themeRegistry.findCategory(id);
break;
}
return def;
}
/**
* Saves the expansion state of the tree.
* @since 3.1
*/
private void saveTreeExpansion() {
Object[] elements = tree.getViewer().getExpandedElements();
List elementIds = new ArrayList(elements.length);
StringBuffer buffer = new StringBuffer();
for (Object object : elements) {
appendMarkerToBuffer(buffer, object);
if (buffer.length() != 0) {
buffer.append(((IThemeElementDefinition) object).getId());
elementIds.add(buffer.toString());
}
buffer.setLength(0);
}
for (Iterator i = elementIds.iterator(); i.hasNext();) {
String id = (String) i.next();
buffer.append(id);
if (i.hasNext()) {
buffer.append(EXPANDED_ELEMENTS_TOKEN);
}
}
getPreferenceStore().setValue(EXPANDED_ELEMENTS_PREF, buffer.toString());
}
private void appendMarkerToBuffer(StringBuffer buffer, Object object) {
if (object instanceof FontDefinition) {
buffer.append(MARKER_FONT);
} else if (object instanceof ColorDefinition) {
buffer.append(MARKER_COLOR);
} else if (object instanceof ThemeElementCategory) {
buffer.append(MARKER_CATEGORY);
}
}
/**
* Edit the currently selected font.
*
* @param display
* the display to open the dialog on
* @since 3.2
*/
private void editFont(Display display) {
editFont(getSelectedFontDefinition(), display);
}
/**
* Edit the given font.
*
* @param definition
* the font definition
* @param display
* the display to open the dialog on
* @since 3.7
*/
private void editFont(FontDefinition definition, Display display) {
if (definition != null) {
final FontDialog fontDialog = new FontDialog(getShell());
fontDialog.setEffectsVisible(false);
fontDialog.setFontList(getFontValue(definition));
final FontData data = fontDialog.open();
if (data != null) {
setFontPreferenceValue(definition, fontDialog.getFontList(), false);
refreshElement(definition);
}
}
}
private void editColor(Display display) {
editColor(getSelectedColorDefinition(), display);
}
private void editColor(ColorDefinition definition, Display display) {
if (definition == null)
return;
RGB currentColor = colorRegistry.getRGB(definition.getId());
ColorDialog colorDialog = new ColorDialog(display.getActiveShell());
colorDialog.setRGB(currentColor);
RGB selectedColor = colorDialog.open();
if ((selectedColor != null) && (!selectedColor.equals(currentColor))) {
setColorPreferenceValue(definition, selectedColor, false);
setRegistryValue(definition, selectedColor);
refreshElement(definition);
}
}
protected void updateControls() {
FontDefinition fontDefinition = getSelectedFontDefinition();
if (fontDefinition != null) {
boolean isDefault = isDefault(fontDefinition);
boolean hasDefault = fontDefinition.getDefaultsTo() != null;
fontChangeButton.setEnabled(true);
fontSystemButton.setEnabled(true);
fontResetButton.setEnabled(!isDefault);
editDefaultButton.setEnabled(hasDefault && isDefault);
goToDefaultButton.setEnabled(hasDefault);
setCurrentFont(fontDefinition);
return;
}
ColorDefinition colorDefinition = getSelectedColorDefinition();
if (colorDefinition != null) {
boolean isDefault = isDefault(getSelectedColorDefinition());
boolean hasDefault = colorDefinition.getDefaultsTo() != null;
fontChangeButton.setEnabled(true);
fontSystemButton.setEnabled(false);
fontResetButton.setEnabled(!isDefault);
editDefaultButton.setEnabled(hasDefault && isDefault);
goToDefaultButton.setEnabled(hasDefault);
setCurrentColor(colorDefinition);
return;
}
// not a font or a color?
fontChangeButton.setEnabled(false);
fontSystemButton.setEnabled(false);
fontResetButton.setEnabled(false);
editDefaultButton.setEnabled(false);
goToDefaultButton.setEnabled(false);
descriptionText.setText(""); //$NON-NLS-1$
}
/**
* @return Return the default "No preview available." preview.
*/
private Composite createNoPreviewControl() {
Composite noPreviewControl = new Composite(previewComposite, SWT.NONE);
noPreviewControl.setLayout(new FillLayout());
Label l = new Label(noPreviewControl, SWT.LEFT);
l.setText(RESOURCE_BUNDLE.getString("noPreviewAvailable")); //$NON-NLS-1$
myApplyDialogFont(l);
return noPreviewControl;
}
private void setCurrentFont(FontDefinition fontDefinition) {
currentFont = fontRegistry.get(fontDefinition.getId());
FontData[] fontData = currentFont != null ? currentFont.getFontData() : new FontData[0];
// recalculate sample text
StringBuffer tmp = new StringBuffer();
for (FontData currentFontData : fontData) {
tmp.append(currentFontData.getName());
tmp.append(' ');
tmp.append(currentFontData.getHeight());
int style = currentFontData.getStyle();
if ((style & SWT.BOLD) != 0) {
tmp.append(' ');
tmp.append(RESOURCE_BUNDLE.getString("boldFont")); //$NON-NLS-1$
}
if ((style & SWT.ITALIC) != 0) {
tmp.append(' ');
tmp.append(RESOURCE_BUNDLE.getString("italicFont")); //$NON-NLS-1$
}
}
fontSampleText = tmp.toString();
descriptionText.setText(fomatDescription(fontDefinition));
fontSampler.redraw();
}
public void setCurrentColor(ColorDefinition colorDefinition) {
currentColor = colorRegistry.get(colorDefinition.getId());
colorSampler.redraw();
descriptionText.setText(fomatDescription(colorDefinition));
}
private Composite createFontPreviewControl() {
fontSampler = new Canvas(previewComposite, SWT.NONE);
GridLayout gridLayout = new GridLayout();
gridLayout.marginWidth = 0;
gridLayout.marginHeight = 0;
fontSampler.setLayout(gridLayout);
fontSampler.setLayoutData(new GridData(GridData.FILL_BOTH));
fontSampler.addPaintListener(e -> {
if (currentFont != null) // do the font preview
paintFontSample(e.gc);
});
return fontSampler;
}
private void paintFontSample(GC gc) {
if (currentFont == null || currentFont.isDisposed())
return;
// draw rectangle all around
Rectangle clientArea = colorSampler.getClientArea();
FontMetrics standardFontMetrics = gc.getFontMetrics();
int standardLineHeight = standardFontMetrics.getHeight();
int maxHeight = standardLineHeight * 4;
if (clientArea.height > maxHeight)
clientArea = new Rectangle(clientArea.x, clientArea.y, clientArea.width, maxHeight);
gc.setForeground(previewComposite.getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
gc.drawRectangle(0, 0, clientArea.width - 1, clientArea.height - 1);
gc.setForeground(previewComposite.getDisplay().getSystemColor(SWT.COLOR_BLACK));
gc.setFont(currentFont);
FontMetrics fontMetrics = gc.getFontMetrics();
int lineHeight = fontMetrics.getHeight();
int topY = clientArea.y + 5;
gc.setClipping(1, 1, clientArea.width - 2, clientArea.height - 2);
gc.drawText(fontSampleText, clientArea.x + 5, topY);
gc.drawText(RESOURCE_BUNDLE.getString("fontTextSample"), clientArea.x + 5, topY + lineHeight); //$NON-NLS-1$
}
private Composite createColorPreviewControl() {
colorSampler = new Canvas(previewComposite, SWT.NONE);
GridLayout gridLayout = new GridLayout();
gridLayout.marginWidth = 0;
gridLayout.marginHeight = 0;
colorSampler.setLayout(gridLayout);
colorSampler.setLayoutData(new GridData(GridData.FILL_BOTH));
colorSampler.addPaintListener(e -> {
if (currentColor != null) // do the color preview
paintColorSample(e.gc);
});
return colorSampler;
}
private void paintColorSample(GC gc) {
if (currentColor == null || currentColor.isDisposed())
return;
gc.setFont(previewComposite.getDisplay().getSystemFont());
FontMetrics fontMetrics = gc.getFontMetrics();
int lineHeight = fontMetrics.getHeight();
Rectangle clientArea = colorSampler.getClientArea();
int maxHeight = lineHeight * 4;
if (clientArea.height > maxHeight)
clientArea = new Rectangle(clientArea.x, clientArea.y, clientArea.width, maxHeight);
String messageTop = RESOURCE_BUNDLE.getString("fontColorSample"); //$NON-NLS-1$
String fontColorString = RESOURCE_BUNDLE.getString("fontColorString"); //$NON-NLS-1$
RGB rgb = currentColor.getRGB();
String messageBottom = MessageFormat.format(fontColorString, Integer.valueOf(rgb.red),
Integer.valueOf(rgb.green), Integer.valueOf(rgb.blue));
// calculate position of the vertical line
int separator = (clientArea.width - 2) / 3;
// calculate text positions
int verticalCenter = clientArea.height / 2;
int textTopY = (verticalCenter - lineHeight) / 2;
if (textTopY < 1)
textTopY = 1;
textTopY += clientArea.y;
int textBottomY = verticalCenter + textTopY;
if (textBottomY > clientArea.height - 2)
textBottomY = clientArea.height - 2;
textBottomY += clientArea.y;
int stringWidthTop = gc.stringExtent(messageTop).x;
int textTopX = (separator - stringWidthTop - 1) / 2;
if (textTopX < 1)
textTopX = 1;
textTopX += clientArea.x;
int stringWidthBottom = gc.stringExtent(messageBottom).x;
int textBottomX = (separator - stringWidthBottom - 1) / 2;
if (textBottomX < 1)
textBottomX = 1;
textBottomX += clientArea.x;
// put text on the left - default background
gc.setForeground(currentColor);
gc.drawText(messageTop, textTopX, textTopY);
gc.drawText(messageBottom, textBottomX, textBottomY);
// fill right rectangle
gc.setBackground(previewComposite.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
int rightWidth = clientArea.width - 2 - separator * 2;
gc.fillRectangle(separator * 2, 1, rightWidth, clientArea.height - 2);
// put text in the right rectangle
gc.setForeground(currentColor);
gc.drawText(messageTop, separator * 2 + textTopX, textTopY);
gc.drawText(messageBottom, separator * 2 + textBottomX, textBottomY);
// fill center rectangle
gc.setBackground(currentColor);
gc.fillRectangle(separator, 1, separator, clientArea.height - 2);
// text: center top
gc.setForeground(previewComposite.getDisplay().getSystemColor(SWT.COLOR_BLACK));
gc.drawText(messageTop, separator + textTopX, textTopY);
gc.setForeground(previewComposite.getDisplay().getSystemColor(SWT.COLOR_WHITE));
gc.drawText(messageBottom, separator + textBottomX, textBottomY);
// niceties
gc.setForeground(previewComposite.getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
gc.drawLine(separator, verticalCenter, separator * 2 - 1, verticalCenter);
// draw rectangle all around
gc.setForeground(previewComposite.getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
gc.drawRectangle(0, 0, clientArea.width - 1, clientArea.height - 1);
}
private void createDescriptionControl(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.marginWidth = 0;
layout.marginHeight = 0;
composite.setLayout(layout);
GridData data = new GridData(GridData.FILL_HORIZONTAL);
data.horizontalSpan = 2;
composite.setLayoutData(data);
Label label = new Label(composite, SWT.LEFT);
label.setText(RESOURCE_BUNDLE.getString("description")); //$NON-NLS-1$
myApplyDialogFont(label);
descriptionText = new Text(composite, SWT.READ_ONLY | SWT.BORDER | SWT.WRAP);
data = new GridData(GridData.FILL_BOTH);
data.heightHint = convertHeightInCharsToPixels(3);
data.widthHint = convertWidthInCharsToPixels(30);
descriptionText.setLayoutData(data);
myApplyDialogFont(descriptionText);
}
private void publishThemeRegistryModifiedEvent() {
if (eventBroker != null) {
eventBroker.send(WorkbenchThemeManager.Events.THEME_REGISTRY_MODIFIED, null);
}
}
private void refreshAllLabels() {
tree.getViewer().refresh(); // refresh all the labels in the tree
}
private boolean isAvailableInCurrentTheme(ThemeElementDefinition definition) {
if (definition instanceof ColorDefinition) {
ColorDefinition colorDef = (ColorDefinition) definition;
RGB value = colorDef.getValue();
if ((value == null || value == EMPTY_COLOR_VALUE) && colorDef.getDefaultsTo() == null) {
return false;
}
return colorRegistry.get(definition.getId()) != null;
}
return true;
}
private String fomatDescription(ThemeElementDefinition definition) {
String description = definition.getDescription() != null ? definition.getDescription() : ""; //$NON-NLS-1$
if (isAvailableInCurrentTheme(definition)) {
return description;
}
return MessageFormat.format(RESOURCE_BUNDLE.getString("definitionNotAvailInTheme"), //$NON-NLS-1$
description).trim();
}
private void updateDefinitionState(ThemeElementDefinition definition, boolean reset) {
if (reset) {
definition.removeState(ThemeElementDefinition.State.MODIFIED_BY_USER);
} else {
definition.appendState(ThemeElementDefinition.State.MODIFIED_BY_USER);
}
}
private void refreshElement(ThemeElementDefinition definition) {
tree.getViewer().refresh(definition);
updateTreeSelection(tree.getViewer().getSelection());
Object newValue = definition instanceof ColorDefinition ?
((ColorDefinition) definition).getValue(): ((FontDefinition) definition).getValue();
getCascadingTheme().fire(new PropertyChangeEvent(this, definition.getId(), null, newValue));
}
private static class CascadingThemeExt extends CascadingTheme {
/**
* @param currentTheme
* @param colorRegistry
* @param fontRegistry
*/
public CascadingThemeExt(ITheme currentTheme, CascadingColorRegistry colorRegistry,
CascadingFontRegistry fontRegistry) {
super(currentTheme, colorRegistry, fontRegistry);
}
@Override
public void fire(PropertyChangeEvent event) {
super.fire(event);
}
}
}