/** * Copyright (c) 2007 Borland Software Corporation * * 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: * bblajer - initial API and implementation */ package org.eclipse.gmf.runtime.lite.properties; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.command.CompoundCommand; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.edit.command.SetCommand; import org.eclipse.emf.transaction.ResourceSetChangeEvent; import org.eclipse.emf.transaction.ResourceSetListener; import org.eclipse.emf.transaction.ResourceSetListenerImpl; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.emf.transaction.util.TransactionUtil; import org.eclipse.gef.EditPart; import org.eclipse.gef.commands.CommandStack; import org.eclipse.gmf.internal.runtime.lite.PluginImages; import org.eclipse.gmf.runtime.lite.commands.WrappingCommand; import org.eclipse.gmf.runtime.notation.NotationPackage; import org.eclipse.gmf.runtime.notation.Style; import org.eclipse.gmf.runtime.notation.View; import org.eclipse.jface.resource.CompositeImageDescriptor; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.accessibility.AccessibleAdapter; import org.eclipse.swt.accessibility.AccessibleEvent; import org.eclipse.swt.custom.CCombo; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.ColorDialog; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.views.properties.tabbed.AbstractPropertySection; import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage; /** * Property section that displays properties for views' font and colors. */ public class ColorsAndFontsPropertySection extends AbstractPropertySection { protected CCombo fontFamilyCombo; private CCombo fontSizeCombo; private Button fontBoldButton; private Button fontItalicButton; protected Button fontColorButton; protected Button lineColorButton; protected Button fillColorButton; protected RGB fontColorRGB; protected RGB lineColorRGB; protected RGB fillColorRGB; private Group colorsAndFontsGroup; private ResourceSetListener myResourceSetListener = new ResourceSetListenerImpl() { @Override public void resourceSetChanged(ResourceSetChangeEvent event) { refresh(); } }; /** * Command stack to execute commands with. If <code>null</code>, all items are read-only. */ private CommandStack myCommandStack; private TransactionalEditingDomain myEditingDomain; private Collection<View> myViews; @Override public void setInput(IWorkbenchPart part, ISelection selection) { if (selection.isEmpty() || false == selection instanceof StructuredSelection) { super.setInput(part, selection); return; } final StructuredSelection structuredSelection = ((StructuredSelection) selection); myViews = new ArrayList<View>(structuredSelection.size()); for (Iterator<?> it = structuredSelection.iterator(); it.hasNext();) { Object r = transformSelection(it.next()); if (r instanceof View) { myViews.add((View) r); } } myEditingDomain = getEditingDomain(myViews); myCommandStack = AdvancedPropertySection.getCommandStack(myEditingDomain); super.setInput(part, new StructuredSelection(myViews)); } private TransactionalEditingDomain getEditingDomain(Collection<View> transformedSelection) { TransactionalEditingDomain result = null; for (View next : transformedSelection) { TransactionalEditingDomain candidate = TransactionUtil.getEditingDomain(next); if (candidate == null) { return null; } if (result != null && result != candidate) { return null; } result = candidate; } return result; } /** * Allows the subclasses to unwrap or otherwise modify the contents of the selection. The <code>null</code> result may be returned, * in this case it will be ignored. * <p/>By default, the passed object is returned untouched. */ protected Object transformSelection(Object selected) { if (selected instanceof EditPart) { EditPart ep = (EditPart) selected; return ep.getModel() instanceof View ? ep.getModel() : null; } if (selected instanceof View) { return selected; } if (selected instanceof IAdaptable) { return ((IAdaptable) selected).getAdapter(View.class); } return null; } @Override public void aboutToBeShown() { if (myEditingDomain != null) { myEditingDomain.addResourceSetListener(getListener()); } } @Override public void aboutToBeHidden() { if (myEditingDomain != null) { myEditingDomain.addResourceSetListener(getListener()); } } protected final ResourceSetListener getListener() { return myResourceSetListener; } @Override public void dispose() { disposeImageFor(fillColorButton, PluginImages.IMG_FILL_COLOR); disposeImageFor(fontColorButton, PluginImages.IMG_FONT_COLOR); disposeImageFor(lineColorButton, PluginImages.IMG_LINE_COLOR); myViews = null; myEditingDomain = null; myCommandStack = null; super.dispose(); } /** * Disposes the image associated with the given button iff it is not a standard image. * @param button * @param key */ protected final void disposeImageFor(Button button, String key) { if (button == null || button.isDisposed()) { return; } Image img = button.getImage(); if (img == PluginImages.get(key)) { //Shared image, do not dispose return; } img.dispose(); } @Override public void refresh() { if (myEditingDomain != null) { boolean isReadOnly = isReadOnly(); refresh(fontFamilyCombo, isReadOnly, NotationPackage.eINSTANCE.getFontStyle_FontName(), ID_CONVERTER); refresh(fontSizeCombo, isReadOnly, NotationPackage.eINSTANCE.getFontStyle_FontHeight(), FROM_INT_CONVERTER); refresh(fontBoldButton, isReadOnly, NotationPackage.eINSTANCE.getFontStyle_Bold()); refresh(fontItalicButton, isReadOnly, NotationPackage.eINSTANCE.getFontStyle_Italic()); fontColorRGB = refreshColor(fontColorButton, isReadOnly, NotationPackage.eINSTANCE.getFontStyle_FontColor(), PluginImages.IMG_FONT_COLOR); lineColorRGB = refreshColor(lineColorButton, isReadOnly, NotationPackage.eINSTANCE.getLineStyle_LineColor(), PluginImages.IMG_LINE_COLOR); fillColorRGB = refreshColor(fillColorButton, isReadOnly, NotationPackage.eINSTANCE.getFillStyle_FillColor(), PluginImages.IMG_FILL_COLOR); } super.refresh(); } private boolean isReadOnly() { if (myCommandStack == null) { return true; } for (View next : myViews) { if (next.eResource() == null || myEditingDomain.isReadOnly(next.eResource())) { return true; } } return false; } protected void refresh(CCombo combo, boolean isReadOnly, final EStructuralFeature feature, IValueConverter<String> converter) { if (!hasStyles(feature.getEContainingClass())) { combo.setEnabled(false); combo.select(-1); return; } combo.setEnabled(!isReadOnly); String result = getStructuralFeatureValue(feature, converter); if (result == null) { combo.select(-1); } else { combo.select(combo.indexOf(result)); } } protected void refresh(Button button, boolean isReadOnly, EStructuralFeature feature) { if (!hasStyles(feature.getEContainingClass())) { button.setEnabled(false); button.setSelection(false); return; } button.setEnabled(!isReadOnly); button.setSelection(getStructuralFeatureValue(feature, TO_BOOLEAN_CONVERTER)); } protected RGB refreshColor(Button button, boolean isReadOnly, EStructuralFeature feature, String baseImageKey) { RGB rgb; if (!hasStyles(feature.getEContainingClass())) { button.setEnabled(false); rgb = null; } else { button.setEnabled(!isReadOnly); rgb = getStructuralFeatureValue(feature, TO_RGB_CONVERTER); } disposeImageFor(button, baseImageKey); if (rgb == null) { button.setImage(PluginImages.get(baseImageKey)); } else { button.setImage(new ColorOverlayImageDescriptor(PluginImages.get(baseImageKey).getImageData(), rgb).createImage()); } //TODO: Update button image based on rgb value return rgb; } protected final boolean hasStyles(EClass styleClass) { for (View next : myViews) { if (next.getStyle(styleClass) == null) { return false; } } return true; } protected <E> E getStructuralFeatureValue(EStructuralFeature feature, IValueConverter<E> converter) { E result = null; for (View next : myViews) { Style style = next.getStyle(feature.getEContainingClass()); if (style == null) { return null; } E nextResult = converter.convertFromStyleValue(style.eGet(feature)); if (nextResult == null) { return null; } if (result == null) { result = nextResult; } else if (!result.equals(nextResult)) { return null; } } return result; } @Override public void createControls(Composite parent, TabbedPropertySheetPage tabbedPropertySheetPage) { super.createControls(parent, tabbedPropertySheetPage); Composite composite = getWidgetFactory().createFlatFormComposite(parent); FormLayout layout = (FormLayout) composite.getLayout(); layout.spacing = 3; createFontsAndColorsGroups(composite); } /** * Create fonts and colors group * @param parent - parent composite */ protected Group createFontsAndColorsGroups(Composite parent) { colorsAndFontsGroup = getWidgetFactory().createGroup(parent, "Fonts and Colors"); colorsAndFontsGroup.setLayout(new GridLayout(1, false)); createFontsGroup(colorsAndFontsGroup); return colorsAndFontsGroup; } /** * Create font tool bar group * @param parent - parent composite * @return - font tool bar */ protected Composite createFontsGroup(Composite parent) { Composite familySize = getWidgetFactory().createComposite(parent); GridLayout layout = new GridLayout(2, false); layout.horizontalSpacing = 0; layout.verticalSpacing = 0; layout.marginHeight = 0; layout.marginWidth = 0; familySize.setLayout(layout); fontFamilyCombo = getWidgetFactory().createCCombo(familySize, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER); fontFamilyCombo.setItems(getFontFamilyNames()); fontFamilyCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { change(fontFamilyCombo, NotationPackage.eINSTANCE.getFontStyle_FontName(), ID_CONVERTER, "Change font family"); } }); fontSizeCombo = getWidgetFactory().createCCombo(familySize,SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER); fontSizeCombo.setItems(getFontSizes()); fontSizeCombo.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { change(fontSizeCombo, NotationPackage.eINSTANCE.getFontStyle_FontHeight(), FROM_INT_CONVERTER, "Change font size"); } }); Composite toolBar = new Composite(parent, SWT.SHADOW_NONE); toolBar.setLayout(new GridLayout(5, false)); toolBar.setBackground(parent.getBackground()); fontBoldButton = new Button(toolBar, SWT.TOGGLE); fontBoldButton.setLayoutData(new GridData(GridData.CENTER, GridData.CENTER, false, false)); fontBoldButton.setImage(PluginImages.get(PluginImages.IMG_BOLD)); fontBoldButton.getAccessible().addAccessibleListener(new AccessibleAdapter() { public void getName(AccessibleEvent e) { e.result = "Bold Font Style"; } }); fontBoldButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { change(fontBoldButton, NotationPackage.eINSTANCE.getFontStyle_Bold(), TO_BOOLEAN_CONVERTER, "Change font bold"); } }); fontItalicButton = new Button(toolBar, SWT.TOGGLE ); fontItalicButton.setLayoutData(new GridData(GridData.CENTER, GridData.CENTER, false, false)); fontItalicButton.setImage(PluginImages.get(PluginImages.IMG_ITALIC)); fontItalicButton.getAccessible().addAccessibleListener(new AccessibleAdapter() { public void getName(AccessibleEvent e) { e.result = "Italic Font Style"; } }); fontItalicButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { change(fontItalicButton, NotationPackage.eINSTANCE.getFontStyle_Italic(), TO_BOOLEAN_CONVERTER, "Change font italic"); } }); fontColorButton = new Button(toolBar, SWT.PUSH); GridData fontColorButtonGridData = new GridData(GridData.CENTER, GridData.CENTER, false, false); fontColorButtonGridData.horizontalIndent = 5; fontColorButton.setLayoutData(fontColorButtonGridData); fontColorButton.setImage(PluginImages.get(PluginImages.IMG_FONT_COLOR)); fontColorButton.getAccessible().addAccessibleListener(new AccessibleAdapter() { public void getName(AccessibleEvent e) { e.result = "Font Color"; } }); fontColorButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { changeColor(fontColorButton, fontColorRGB, NotationPackage.eINSTANCE.getFontStyle_FontColor(), "Change font color"); } }); lineColorButton = new Button(toolBar, SWT.PUSH); GridData lineColorButtonGridData = new GridData(GridData.CENTER, GridData.CENTER, false, false); lineColorButtonGridData.horizontalIndent = 5; fontColorButton.setLayoutData(lineColorButtonGridData); lineColorButton.setImage(PluginImages.get(PluginImages.IMG_LINE_COLOR)); lineColorButton.getAccessible().addAccessibleListener(new AccessibleAdapter() { public void getName(AccessibleEvent e) { e.result = "Line Color"; } }); lineColorButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { changeColor(lineColorButton, lineColorRGB, NotationPackage.eINSTANCE.getLineStyle_LineColor(), "Change line color"); } }); fillColorButton = new Button(toolBar, SWT.PUSH); fillColorButton.setLayoutData(new GridData(GridData.CENTER, GridData.CENTER, false, false)); fillColorButton.setImage(PluginImages.get(PluginImages.IMG_FILL_COLOR)); fillColorButton.getAccessible().addAccessibleListener(new AccessibleAdapter() { public void getName(AccessibleEvent e) { e.result = "Fill Color"; } }); fillColorButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { changeColor(fillColorButton, fillColorRGB, NotationPackage.eINSTANCE.getFillStyle_FillColor(), "Change fill color"); } }); return toolBar; } protected void change(CCombo combo, EStructuralFeature feature, IValueConverter<String> converter, String commandName) { if (combo.getSelectionIndex() == -1) { return; } String selectedValue = combo.getText(); if (selectedValue == null || selectedValue.length() == 0) { selectedValue = null; } Object value = converter.convertToStyleValue(selectedValue); executeChange(commandName, feature, value); } protected void change(Button button, EStructuralFeature feature, IValueConverter<Boolean> converter, String commandName) { boolean isSelected = button.getSelection(); Object value = converter.convertToStyleValue(isSelected); executeChange(commandName, feature, value); } protected void changeColor(Button button, RGB currentRGB, EStructuralFeature feature, String commandName) { //TODO: make it possible to revert to default color (reuse from parent) ColorDialog colorDialog = new ColorDialog(button.getShell()); colorDialog.setRGB(currentRGB); RGB newRGB = colorDialog.open(); if (newRGB == null || newRGB.equals(currentRGB)) { return; } Object featureValue = TO_RGB_CONVERTER.convertToStyleValue(newRGB); executeChange(commandName, feature, featureValue); } /** * Creates and executes the command that updates the styles of the given views */ protected void executeChange(String commandName, EStructuralFeature feature, Object value) { if (feature.isRequired() && value == null) { value = SetCommand.UNSET_VALUE; } CompoundCommand cc = new CompoundCommand(commandName); for (View next : myViews) { Style style = next.getStyle(feature.getEContainingClass()); Command command = SetCommand.create(myEditingDomain, style, feature, value); cc.append(command); } if (cc.canExecute()) { myCommandStack.execute(new WrappingCommand(myEditingDomain, cc)); } } private static String[] getFontFamilyNames() { if (FONT_FAMILY_NAMES == null) { FontData[] fontFamilies = Display.getDefault().getFontList(null, true); Set<String> fontFamilyNames = new HashSet<String>(fontFamilies.length * 3/2); for (FontData next : fontFamilies) { fontFamilyNames.add(next.getName()); } fontFamilyNames.remove(null); //in case it was there fontFamilyNames.add(""); //$NON-NLS-1$ //to denote the default font FONT_FAMILY_NAMES = fontFamilyNames.toArray(new String[fontFamilyNames.size()]); Arrays.sort(FONT_FAMILY_NAMES); } return FONT_FAMILY_NAMES; } private static String[] FONT_FAMILY_NAMES; private static String[] getFontSizes() { return FONT_SIZES; } private static final String[] FONT_SIZES = new String[] { String.valueOf(8), String.valueOf(9), String.valueOf(10), String.valueOf(11), String.valueOf(12), String.valueOf(14), String.valueOf(16), String.valueOf(18), String.valueOf(20), String.valueOf(22), String.valueOf(24), String.valueOf(26), String.valueOf(28), String.valueOf(36), String.valueOf(48), String.valueOf(72), }; protected static interface IValueConverter<E> { E convertFromStyleValue(Object styleValue); Object convertToStyleValue(E literalValue); } private static final IValueConverter<Boolean> TO_BOOLEAN_CONVERTER = new IValueConverter<Boolean>() { public Boolean convertFromStyleValue(Object value) { return value instanceof Boolean ? (Boolean) value : Boolean.FALSE; } public Object convertToStyleValue(Boolean literalValue) { return literalValue; } }; private static final IValueConverter<RGB> TO_RGB_CONVERTER = new IValueConverter<RGB>() { public RGB convertFromStyleValue(Object styleValue) { int value = ((Integer) styleValue).intValue(); return new RGB((value & 0x000000FF), (value & 0x0000FF00) >> 8, (value & 0x00FF0000) >> 16); } public Object convertToStyleValue(RGB rgb) { return new Integer((rgb.blue << 16) | (rgb.green << 8) | rgb.red); } }; private static final IValueConverter<String> FROM_INT_CONVERTER = new IValueConverter<String>() { public String convertFromStyleValue(Object styleValue) { if (styleValue == null) { return null; } return String.valueOf(styleValue); } public Object convertToStyleValue(String literalValue) { if (literalValue == null) { return null; } return Integer.parseInt(literalValue); } }; private static final IValueConverter<String> ID_CONVERTER = new IValueConverter<String>() { public String convertFromStyleValue(Object styleValue) { return (String) styleValue; } public Object convertToStyleValue(String literalValue) { return literalValue; } }; /** * Image descriptor which draws a uniform color underneath the lower portion of the given image. * <p>This class is a copy of org.eclipse.gmf.runtime.diagram.ui.properties.sections.appearance.ColorsAndFontsPropertySection.ColorOverlayImageDescriptor with little modification. */ protected static class ColorOverlayImageDescriptor extends CompositeImageDescriptor { private final ImageData myOriginal; private final RGB mySubstrate; private final Point mySize; public ColorOverlayImageDescriptor(ImageData original, RGB substrate) { myOriginal = original; mySubstrate = substrate; mySize = new Point(myOriginal.width, myOriginal.height); } @Override protected Point getSize() { return mySize; } @Override protected void drawCompositeImage(int width, int height) { ImageData substrate = new ImageData(width, height/5, 1, new PaletteData(new RGB[] {mySubstrate})); drawImage(substrate, 0, height - substrate.height); drawImage(myOriginal, 0, 0); } } }