From b13cc3c964fee8f16bfbf4bd42ad0631c4cf7889 Mon Sep 17 00:00:00 2001 From: Tor Norbye Date: Thu, 22 Mar 2012 21:38:45 -0700 Subject: Add WindowBuilder propertysheet code. See README.txt for details. This reverts commit c0ecc89c7fa48c6e5ab3c7635863a6e8d2b28137. --- .../eclipse/wb/internal/core/DesignerPlugin.java | 229 +++ .../eclipse/wb/internal/core/EnvironmentUtils.java | 33 + .../property/PropertyListIntersector.java | 136 ++ .../internal/core/icons/properties/BooleanNull.png | Bin 0 -> 213 bytes .../core/icons/properties/BooleanUnknown.png | Bin 0 -> 362 bytes .../wb/internal/core/icons/properties/dots.gif | Bin 0 -> 825 bytes .../wb/internal/core/icons/properties/down.png | Bin 0 -> 141 bytes .../wb/internal/core/icons/properties/false.png | Bin 0 -> 155 bytes .../wb/internal/core/icons/properties/minus.gif | Bin 0 -> 872 bytes .../wb/internal/core/icons/properties/plus.gif | Bin 0 -> 873 bytes .../wb/internal/core/icons/properties/true.png | Bin 0 -> 214 bytes .../wb/internal/core/model/ModelMessages.java | 27 + .../internal/core/model/ModelMessages.properties | 13 + .../core/model/property/ComplexProperty.java | 209 +++ .../core/model/property/EmptyProperty.java | 59 + .../wb/internal/core/model/property/Property.java | 232 +++ .../core/model/property/PropertyManager.java | 36 + .../model/property/category/PropertyCategory.java | 159 ++ .../category/PropertyCategoryProvider.java | 26 + .../category/PropertyCategoryProviders.java | 85 ++ .../editor/AbstractComboBoxPropertyEditor.java | 156 ++ .../editor/AbstractComboPropertyEditor.java | 153 ++ .../editor/AbstractListPropertyEditor.java | 173 +++ .../editor/AbstractTextPropertyEditor.java | 306 ++++ .../editor/BooleanObjectPropertyEditor.java | 118 ++ .../property/editor/BooleanPropertyEditor.java | 112 ++ .../property/editor/CharacterPropertyEditor.java | 80 + .../editor/DoubleObjectPropertyEditor.java | 92 ++ .../property/editor/DoublePropertyEditor.java | 84 + .../editor/EnumerationValuesPropertyEditor.java | 129 ++ .../model/property/editor/FloatPropertyEditor.java | 83 + .../property/editor/ITextValuePropertyEditor.java | 26 + .../editor/IValueSourcePropertyEditor.java | 25 + .../editor/IntegerObjectPropertyEditor.java | 92 ++ .../property/editor/IntegerPropertyEditor.java | 84 + .../property/editor/LocalePropertyEditor.java | 69 + .../property/editor/LongObjectPropertyEditor.java | 92 ++ .../model/property/editor/LongPropertyEditor.java | 83 + .../editor/PropertyDescriptorEditorProvider.java | 73 + .../core/model/property/editor/PropertyEditor.java | 117 ++ .../property/editor/PropertyEditorProvider.java | 44 + .../property/editor/ShortObjectPropertyEditor.java | 92 ++ .../model/property/editor/ShortPropertyEditor.java | 83 + .../property/editor/StringArrayPropertyEditor.java | 80 + .../property/editor/StringComboPropertyEditor.java | 65 + .../property/editor/StringListPropertyEditor.java | 95 ++ .../property/editor/TextControlActionsManager.java | 46 + .../property/editor/TextDialogPropertyEditor.java | 64 + .../property/editor/TextDisplayPropertyEditor.java | 66 + .../editor/complex/IComplexPropertyEditor.java | 27 + .../ButtonPropertyEditorPresentation.java | 114 ++ .../ButtonPropertyEditorPresentationImpl.java | 224 +++ .../ButtonPropertyEditorPresentationImplMac.java | 57 + .../CompoundPropertyEditorPresentation.java | 70 + .../presentation/PropertyEditorPresentation.java | 41 + .../editor/string/StringPropertyDialog.java | 141 ++ .../editor/string/StringPropertyEditor.java | 99 ++ .../model/property/table/HtmlTooltipHelper.java | 332 ++++ .../property/table/IPropertyExceptionHandler.java | 25 + .../model/property/table/IPropertyTooltipSite.java | 30 + .../core/model/property/table/PropertyTable.java | 1602 ++++++++++++++++++++ .../property/table/PropertyTableTooltipHelper.java | 191 +++ .../property/table/PropertyTooltipProvider.java | 117 ++ .../table/PropertyTooltipTextProvider.java | 67 + .../internal/core/model/property/table/Tooltip.css | 35 + .../org/eclipse/wb/internal/core/utils/Pair.java | 81 + .../controls/AbstractControlActionsManager.java | 174 +++ .../controls/DefaultControlActionsManager.java | 70 + .../wb/internal/core/utils/check/Assert.java | 361 +++++ .../core/utils/check/AssertionFailedException.java | 37 + .../core/utils/execution/ExecutionUtils.java | 292 ++++ .../internal/core/utils/execution/RunnableEx.java | 24 + .../core/utils/execution/RunnableObjectEx.java | 26 + .../core/utils/reflect/ClassLoaderLocalMap.java | 192 +++ .../wb/internal/core/utils/reflect/ClassMap.java | 75 + .../core/utils/reflect/ReflectionUtils.java | 327 ++++ .../wb/internal/core/utils/ui/DrawUtils.java | 337 ++++ .../wb/internal/core/utils/ui/GridDataFactory.java | 541 +++++++ .../internal/core/utils/ui/GridLayoutFactory.java | 123 ++ .../core/utils/ui/ImageImageDescriptor.java | 44 + .../wb/internal/core/utils/ui/PixelConverter.java | 72 + .../eclipse/wb/internal/core/utils/ui/UiUtils.java | 42 + .../core/utils/ui/dialogs/ResizableDialog.java | 221 +++ .../core/utils/ui/dialogs/StringsDialog.java | 77 + .../internal/core/utils/ui/dialogs/TextDialog.java | 118 ++ 85 files changed, 10432 insertions(+) create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/DesignerPlugin.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/EnvironmentUtils.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/editor/structure/property/PropertyListIntersector.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanNull.png create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanUnknown.png create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/icons/properties/dots.gif create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/icons/properties/down.png create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/icons/properties/false.png create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/icons/properties/minus.gif create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/icons/properties/plus.gif create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/icons/properties/true.png create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.properties create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/ComplexProperty.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/EmptyProperty.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/Property.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/PropertyManager.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategory.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProvider.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProviders.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboBoxPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractListPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractTextPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanObjectPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/CharacterPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoubleObjectPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoublePropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/EnumerationValuesPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/FloatPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ITextValuePropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IValueSourcePropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerObjectPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LocalePropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongObjectPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyDescriptorEditorProvider.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditorProvider.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortObjectPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringArrayPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringComboPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringListPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextControlActionsManager.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDialogPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDisplayPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/complex/IComplexPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentation.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImpl.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImplMac.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/CompoundPropertyEditorPresentation.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/PropertyEditorPresentation.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyDialog.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyEditor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyExceptionHandler.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyTooltipSite.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTableTooltipHelper.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipProvider.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipTextProvider.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/model/property/table/Tooltip.css create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/Pair.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/AbstractControlActionsManager.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/DefaultControlActionsManager.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/check/Assert.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/check/AssertionFailedException.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/execution/ExecutionUtils.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableEx.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableObjectEx.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassLoaderLocalMap.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassMap.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ReflectionUtils.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridDataFactory.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridLayoutFactory.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/ui/ImageImageDescriptor.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/ui/PixelConverter.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/ui/UiUtils.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/ResizableDialog.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/StringsDialog.java create mode 100644 propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/TextDialog.java (limited to 'propertysheet/src/org/eclipse/wb/internal') diff --git a/propertysheet/src/org/eclipse/wb/internal/core/DesignerPlugin.java b/propertysheet/src/org/eclipse/wb/internal/core/DesignerPlugin.java new file mode 100644 index 0000000..564a068 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/DesignerPlugin.java @@ -0,0 +1,229 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ + +package org.eclipse.wb.internal.core; + +import com.google.common.collect.Maps; +import com.google.common.io.CharStreams; +import com.google.common.io.Closeables; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.plugin.AbstractUIPlugin; + +import java.io.Closeable; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Map; + +/** + * The DesignerPlugin class is the "nexus" of the propertysheet. In WindowBuilder, + * it's the plugin activator, and contains a number of important utility methods, such + * as resource loading, logging, obtaining a display and shell, etc. + *

+ * In the AOSP fork, most of the functionality has been ripped out, except for the + * above mentioned pieces, and this class is no longer a plugin. Instead, it *delegates* + * to the plugin which initializes it via the {@link #initialize} method for things + * like logging. For things like image loading, it has its own local code such that + * it can find its image resources locally instead of requiring the embedding plugin + * to copy the images into its own jar. + *

+ * "DesignerPlugin" is not a very good name for this class since it is not a plugin, + * but it was left that way to avoid modifying all the various propertysheet classes; + * we'd like to keep those as unmodified as possible to make absorbing future + * WindowBuilder improvements as easy as possible. + */ +public class DesignerPlugin { + private static AbstractUIPlugin sPlugin; + private static String sPluginId; + + /** + * Initialize the property sheet for use in the ADT plugin + * + * @param hostPlugin the plugin to embed the property sheet + * @param pluginId the id of the plugin to use in status messages etc + * @param isWindows whether we're running on Windows + * @param isMac whether we're running on Mac + * @param isLinux whether we're running on Linux + */ + public static void initialize(AbstractUIPlugin hostPlugin, String pluginId, + boolean isWindows, boolean isMac, boolean isLinux) { + assert sPlugin == null; // Can only be used by one client in the same classloader + sPlugin = hostPlugin; + sPluginId = pluginId; + EnvironmentUtils.IS_WINDOWS = isWindows; + EnvironmentUtils.IS_MAC = isMac; + EnvironmentUtils.IS_LINUX = isLinux; + } + + /** + * Dispose the propertysheet library: free up images from the cache, unregister the + * plugin reference etc. + */ + public static void dispose() { + sPlugin = null; + for (Image image : sImageCache.values()) { + image.dispose(); + } + sImageCache.clear(); + sDescriptorCache.clear(); + } + + /** + * Reads the contents of an {@link InputStreamReader} using the default + * platform encoding and return it as a String. This method will close the + * input stream. + * + * @param inputStream the input stream to be read from + * @param charset the charset to use + * @return the String read from the stream, or null if there was an error + */ + public static String readFile(InputStream inputStream, Charset charset) { + if (inputStream == null) { + return null; + } + Closeable closeMe = inputStream; + try { + final InputStreamReader isr = new InputStreamReader(inputStream, charset); + closeMe = isr; + try { + return CharStreams.toString(isr); + } catch (Exception ioe) { + // pass -- ignore files we can't read + return null; + } + } finally { + Closeables.closeQuietly(closeMe); + } + } + + /** + * @return the instance of {@link DesignerPlugin} + */ + public static AbstractUIPlugin getDefault() { + assert sPlugin != null; + return sPlugin; + } + + // ////////////////////////////////////////////////////////////////////////// + // + // Display/Shell + // + // ////////////////////////////////////////////////////////////////////////// + /** + * @return the {@link Display} instance, current (if in GUI thread) or + * default. + */ + public static Display getStandardDisplay() { + Display display = Display.getCurrent(); + if (display == null) { + display = Display.getDefault(); + } + return display; + } + + /** + * @return the active {@link IWorkbenchWindow}. + */ + public static IWorkbenchWindow getActiveWorkbenchWindow() { + return getDefault().getWorkbench().getActiveWorkbenchWindow(); + } + + /** + * @return the {@link Shell} of active {@link IWorkbenchWindow}. + */ + public static Shell getShell() { + if (getActiveWorkbenchWindow() != null) { + return getActiveWorkbenchWindow().getShell(); + } + return null; + } + + /** + * Logs given {@link IStatus} into Eclipse .log. + */ + public static void log(IStatus status) { + getDefault().getLog().log(status); + } + + /** + * Logs {@link IStatus} with given message into Eclipse .log. + */ + public static void log(String message) { + log(new Status(IStatus.INFO, sPluginId, IStatus.INFO, message, null)); + } + + /** + * Logs {@link IStatus} with given exception into Eclipse .log. + */ + public static void log(Throwable e) { + Status status = new Status(IStatus.ERROR, sPluginId, "", e); + getDefault().getLog().log(status); + } + + /** + * Logs {@link IStatus} with given message and exception into Eclipse .log. + */ + public static void log(String message, Throwable e) { + log(createStatus(message, e)); + } + + /** + * Creates {@link IStatus} for given message and exception. + */ + public static Status createStatus(String message, Throwable e) { + return new Status(IStatus.ERROR, "wb", IStatus.ERROR, message, e) { + @Override + public boolean isMultiStatus() { + return true; + } + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // + // Resources + // + // ////////////////////////////////////////////////////////////////////////// + private static Map sDescriptorCache = Maps.newHashMap(); + private static Map sImageCache = Maps.newHashMap(); + + public static Image getImage(String path) { + Image image = sImageCache.get(path); + if (image == null) { + ImageDescriptor descriptor = getImageDescriptor(path); + if (descriptor != null) { + return descriptor.createImage(); + } + sImageCache.put(path, image); + } + return image; + } + + public static ImageDescriptor getImageDescriptor(String path) { + ImageDescriptor descriptor = sDescriptorCache.get(path); + if (descriptor == null) { + URL url = DesignerPlugin.class.getResource("icons/" + path); //$NON-NLS-1$ + if (url != null) { + descriptor = ImageDescriptor.createFromURL(url); + sDescriptorCache.put(path, descriptor); + } + } + return descriptor; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/EnvironmentUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/EnvironmentUtils.java new file mode 100644 index 0000000..cedbbc0 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/EnvironmentUtils.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core; + +import org.eclipse.ui.plugin.AbstractUIPlugin; + +/** + * Helper for environment state access. + * + * @author scheglov_ke + * @coverage core + */ +public final class EnvironmentUtils extends AbstractUIPlugin { + //////////////////////////////////////////////////////////////////////////// + // + // Operating systems + // + //////////////////////////////////////////////////////////////////////////// + /** True if this is running on Windows */ + public static boolean IS_WINDOWS; + /** True if this is running on Mac */ + public static boolean IS_MAC; + /** True if this is running on Linux */ + public static boolean IS_LINUX; +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/property/PropertyListIntersector.java b/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/property/PropertyListIntersector.java new file mode 100644 index 0000000..24aea2f --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/property/PropertyListIntersector.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.editor.structure.property; + +import com.google.common.collect.Lists; + +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.PropertyManager; + +import java.util.Iterator; +import java.util.List; + +/** + * Helper for computing intersection of {@link Property} arrays. + * + * @author scheglov_ke + * @coverage core.editor.structure + */ +public final class PropertyListIntersector { + private List m_intersection; + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + /** + * Updates intersection by intersecting with new given array. + */ + public void intersect(Property[] properties) { + if (m_intersection == null) { + m_intersection = Lists.newArrayList(); + for (int i = 0; i < properties.length; i++) { + Property property = properties[i]; + m_intersection.add(new PropertyGroup(property)); + } + } else { + for (Iterator I = m_intersection.iterator(); I.hasNext();) { + PropertyGroup propertyGroup = I.next(); + if (!propertyGroup.add(properties)) { + I.remove(); + } + } + } + } + + /** + * @return the array of matched composite {@link Property}'s. + */ + public Property[] getProperties() { + List properties = Lists.newArrayList(); + for (PropertyGroup propertyGroup : m_intersection) { + Property compositeProperty = propertyGroup.getCompositeProperty(); + if (compositeProperty != null) { + properties.add(compositeProperty); + } + } + // + return properties.toArray(new Property[properties.size()]); + } + + //////////////////////////////////////////////////////////////////////////// + // + // PropertyGroup + // + //////////////////////////////////////////////////////////////////////////// + /** + * The group of {@link Property}'s that match. + */ + private static final class PropertyGroup { + private final List m_properties = Lists.newArrayList(); + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public PropertyGroup(Property property) { + m_properties.add(property); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return true if new matched {@link Property} from given array was added. + */ + public boolean add(Property[] properties) { + for (Property property : properties) { + if (add(property)) { + return true; + } + } + // no match + return false; + } + + /** + * @return the composite {@link Property} for this group. + */ + public Property getCompositeProperty() { + Property properties[] = m_properties.toArray(new Property[m_properties.size()]); + return properties[0].getComposite(properties); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Internal + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return true if given {@link Property} matches and was added. + */ + private boolean add(Property property) { + Property example = m_properties.get(0); + if (example.getClass() == property.getClass() + && example.getTitle().equals(property.getTitle()) + && PropertyManager.getCategory(example) == PropertyManager.getCategory(property)) { + m_properties.add(property); + return true; + } + // no match + return false; + } + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanNull.png b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanNull.png new file mode 100644 index 0000000..437b546 Binary files /dev/null and b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanNull.png differ diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanUnknown.png b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanUnknown.png new file mode 100644 index 0000000..e92cbf0 Binary files /dev/null and b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanUnknown.png differ diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/dots.gif b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/dots.gif new file mode 100644 index 0000000..e3c6e83 Binary files /dev/null and b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/dots.gif differ diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/down.png b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/down.png new file mode 100644 index 0000000..57147b8 Binary files /dev/null and b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/down.png differ diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/false.png b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/false.png new file mode 100644 index 0000000..7e1094f Binary files /dev/null and b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/false.png differ diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/minus.gif b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/minus.gif new file mode 100644 index 0000000..e6579a6 Binary files /dev/null and b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/minus.gif differ diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/plus.gif b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/plus.gif new file mode 100644 index 0000000..3944fed Binary files /dev/null and b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/plus.gif differ diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/true.png b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/true.png new file mode 100644 index 0000000..7908e7a Binary files /dev/null and b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/true.png differ diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.java b/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.java new file mode 100644 index 0000000..58bc631 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.java @@ -0,0 +1,27 @@ +package org.eclipse.wb.internal.core.model; + +import org.eclipse.osgi.util.NLS; + +public class ModelMessages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.wb.internal.core.model.ModelMessages"; //$NON-NLS-1$ + public static String CharacterPropertyEditor_notValid; + public static String DoubleObjectPropertyEditor_notValidDouble; + public static String DoublePropertyEditor_notValidDouble; + public static String FloatPropertyEditor_notValidFloat; + public static String IntegerObjectPropertyEditor_notValidInt; + public static String IntegerPropertyEditor_notValidInt; + public static String LongObjectPropertyEditor_notValidLong; + public static String LongPropertyEditor_notValidLong; + public static String ShortObjectPropertyEditor_notValidShort; + public static String ShortPropertyEditor_notValidShort; + public static String StringArrayPropertyEditor_hint; + public static String StringArrayPropertyEditor_itemsLabel; + public static String StringPropertyDialog_title; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, ModelMessages.class); + } + + private ModelMessages() { + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.properties b/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.properties new file mode 100644 index 0000000..6801adb --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.properties @@ -0,0 +1,13 @@ +CharacterPropertyEditor_notValid="{0}" is not a valid character. +DoubleObjectPropertyEditor_notValidDouble="{0}" is not a valid double. +DoublePropertyEditor_notValidDouble="{0}" is not a valid double. +FloatPropertyEditor_notValidFloat="{0}" is not a valid float. +IntegerObjectPropertyEditor_notValidInt="{0}" is not a valid int. +IntegerPropertyEditor_notValidInt="{0}" is not a valid int. +LongObjectPropertyEditor_notValidLong="{0}" is not a valid long. +LongPropertyEditor_notValidLong="{0}" is not a valid long. +ShortObjectPropertyEditor_notValidShort="{0}" is not a valid short. +ShortPropertyEditor_notValidShort="{0}" is not a valid short. +StringArrayPropertyEditor_hint=Each line in the above text field represents single element. +StringArrayPropertyEditor_itemsLabel=&Elements: +StringPropertyDialog_title=String editor diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/ComplexProperty.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/ComplexProperty.java new file mode 100644 index 0000000..69bb455 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/ComplexProperty.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property; + +import org.eclipse.wb.internal.core.model.property.editor.TextDisplayPropertyEditor; +import org.eclipse.wb.internal.core.model.property.editor.complex.IComplexPropertyEditor; +import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; +import org.eclipse.wb.internal.core.model.property.table.PropertyTooltipProvider; +import org.eclipse.wb.internal.core.model.property.table.PropertyTooltipTextProvider; + +import org.eclipse.swt.graphics.Point; + +import java.util.List; + +/** + * Implementation of {@link Property} that shows given inner {@link Property}'s using + * {@link IComplexPropertyEditor}. + * + * @author scheglov_ke + * @coverage core.model.property + */ +public class ComplexProperty extends Property { + private final String m_title; + private String m_text; + private String m_tooltip; + private boolean m_modified; + private Property[] m_properties; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructors + // + //////////////////////////////////////////////////////////////////////////// + public ComplexProperty(String title, String text) { + this(title, text, new Property[0]); + } + + public ComplexProperty(String title, String text, Property[] properties) { + super(new ComplexPropertyEditor()); + m_title = title; + m_text = text; + setText(text); + setProperties(properties); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sets the text. + */ + public void setText(String text) { + m_text = text; + } + + /** + * @return the text to display as value. + */ + public String getText() throws Exception { + return m_text; + } + + /** + * Sets the tooltip text. + */ + public void setTooltip(String tooltip) { + m_tooltip = tooltip; + } + + /** + * Specifies the {@link PropertyEditorPresentation}, for example to displaying "..." button. + */ + public void setEditorPresentation(PropertyEditorPresentation presentation) { + ((ComplexPropertyEditor) getEditor()).m_presentation = presentation; + } + + /** + * @return the sub-properties. + */ + public Property[] getProperties() { + return m_properties; + } + + /** + * Sets the sub-properties. + */ + public void setProperties(Property[] properties) { + m_properties = properties; + } + + /** + * Sets the sub-properties. + */ + public void setProperties(List properties) { + Property[] propertiesArray = properties.toArray(new Property[properties.size()]); + setProperties(propertiesArray); + } + + /** + * Sets the "modified" flag. + */ + public void setModified(boolean modified) { + m_modified = modified; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Property + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getTitle() { + return m_title; + } + + @Override + public boolean isModified() throws Exception { + return m_modified; + } + + @Override + public Object getValue() throws Exception { + return null; + } + + @Override + public void setValue(Object value) throws Exception { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Adapter + // + //////////////////////////////////////////////////////////////////////////// + @Override + public T getAdapter(Class adapter) { + if (adapter == PropertyTooltipProvider.class && m_tooltip != null) { + return adapter.cast(new PropertyTooltipTextProvider() { + @Override + protected String getText(Property property) throws Exception { + return m_tooltip; + } + }); + } + return super.getAdapter(adapter); + } + + //////////////////////////////////////////////////////////////////////////// + // + // ComplexPropertyEditor + // + //////////////////////////////////////////////////////////////////////////// + private static final class ComplexPropertyEditor extends TextDisplayPropertyEditor + implements + IComplexPropertyEditor { + private PropertyEditorPresentation m_presentation; + + //////////////////////////////////////////////////////////////////////////// + // + // IComplexPropertyEditor + // + //////////////////////////////////////////////////////////////////////////// + public Property[] getProperties(Property property) throws Exception { + return ((ComplexProperty) property).getProperties(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // TextDisplayPropertyEditor + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getText(Property property) throws Exception { + return ((ComplexProperty) property).getText(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // PropertyEditor + // + //////////////////////////////////////////////////////////////////////////// + @Override + public boolean activate(PropertyTable propertyTable, Property property, Point location) + throws Exception { + return false; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public PropertyEditorPresentation getPresentation() { + return m_presentation; + } + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/EmptyProperty.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/EmptyProperty.java new file mode 100644 index 0000000..0f12a5a --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/EmptyProperty.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property; + +import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor; +import org.eclipse.wb.internal.core.model.property.editor.string.StringPropertyEditor; + +/** + * Empty {@link Property}, that has no title or value. + * + * @author scheglov_ke + * @coverage core.model.property + */ +public class EmptyProperty extends Property { + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public EmptyProperty() { + super(StringPropertyEditor.INSTANCE); + } + + public EmptyProperty(PropertyEditor editor) { + super(editor); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Property + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getTitle() { + return null; + } + + @Override + public boolean isModified() throws Exception { + return false; + } + + @Override + public Object getValue() throws Exception { + return UNKNOWN_VALUE; + } + + @Override + public void setValue(Object value) throws Exception { + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/Property.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/Property.java new file mode 100644 index 0000000..28afcd3 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/Property.java @@ -0,0 +1,232 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property; + +import com.google.common.collect.Maps; + +import org.eclipse.wb.internal.core.model.property.category.PropertyCategory; +import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor; + +import java.util.Comparator; +import java.util.Map; + +/** + * {@link Property} is used to display/change properties of ObjectInfo's. + * + * @author scheglov_ke + * @coverage core.model.property + */ +public abstract class Property { + /** + * The value that should be used when we don't know real value of {@link Property}. We can not use + * null because null can be valid value. + */ + public static final Object UNKNOWN_VALUE = new Object() { + @Override + public String toString() { + return "UNKNOWN_VALUE"; + } + }; + //////////////////////////////////////////////////////////////////////////// + // + // Instance fields + // + //////////////////////////////////////////////////////////////////////////// + protected final PropertyEditor m_editor; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public Property(PropertyEditor editor) { + m_category = PropertyCategory.NORMAL; + m_editor = editor; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the title displayed to the user to identify the property. + */ + public abstract String getTitle(); + + /** + * @return true if this property has a non-default value + */ + public abstract boolean isModified() throws Exception; + + //////////////////////////////////////////////////////////////////////////// + // + // Category + // + //////////////////////////////////////////////////////////////////////////// + private PropertyCategory m_category; + + /** + * @return current {@link PropertyCategory}. + */ + public final PropertyCategory getCategory() { + return m_category; + } + + /** + * Sets the {@link PropertyCategory} for this {@link Property}. + */ + public final void setCategory(PropertyCategory category) { + m_category = category; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Value + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the current value of this {@link Property} or {@link #UNKNOWN_VALUE}. + */ + public abstract Object getValue() throws Exception; + + /** + * Sets the new value of this {@link Property}. + * + * @param the + * new value of {@link Property} or {@link #UNKNOWN_VALUE} if {@link Property} + * modification should be removed. + */ + public abstract void setValue(Object value) throws Exception; + + //////////////////////////////////////////////////////////////////////////// + // + // Editor + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the {@link PropertyEditor}. + */ + public final PropertyEditor getEditor() { + return m_editor; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Composite + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the composite {@link Property} for given array of {@link Property}'s or + * null if no composite {@link Property} can be created. + */ + public Property getComposite(Property[] properties) { + return null; + } + + public T getAdapter(Class adapter) { + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Arbitrary values map + // + //////////////////////////////////////////////////////////////////////////// + private Map m_arbitraryMap; + + /** + * Associates the given value with the given key. + */ + public final void putArbitraryValue(Object key, Object value) { + if (m_arbitraryMap == null) { + m_arbitraryMap = Maps.newHashMap(); + } + m_arbitraryMap.put(key, value); + } + + /** + * @return the value to which the given key is mapped, or null. + */ + public final Object getArbitraryValue(Object key) { + if (m_arbitraryMap != null) { + return m_arbitraryMap.get(key); + } + return null; + } + + /** + * Removes the mapping for a key. + */ + public final void removeArbitraryValue(Object key) { + if (m_arbitraryMap != null) { + m_arbitraryMap.remove(key); + } + } + + // BEGIN ADT MODIFICATIONS + + /** + * Returns the name of the property (which is not always the same as the + * title; for example, the "maxWidth" property has title "Max Width" and + * name "maxWidth". + *

+ * This is shown in tooltips to users etc to make it clear what they should + * use in their own code. + * + * @return the name of the property + */ + public String getName() { + return getTitle(); + } + + private int mPriority; + + /** + * Gets the custom sort priority of this property + * + * @return the sort priority + */ + public int getPriority() { + return mPriority; + } + + /** + * Sets the custom sort priority of this property + * + * @param priority the new priority to use + */ + public void setPriority(int priority) { + this.mPriority = priority; + } + + /** Sort {@link Property} instances alphabetically by property name */ + public static final Comparator ALPHABETICAL = new Comparator() { + @Override + public int compare(Property p1, Property p2) { + return p1.getName().compareTo(p2.getName()); + } + }; + + /** Sort {@link Property} instances by priority */ + public static final Comparator PRIORITY = new Comparator() { + @Override + public int compare(Property p1, Property p2) { + int delta = p1.mPriority - p2.mPriority; + if (delta != 0) { + return delta; + } + + return p1.getName().compareTo(p2.getName()); + } + }; + // END ADT MODIFICATIONS +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/PropertyManager.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/PropertyManager.java new file mode 100644 index 0000000..7152999 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/PropertyManager.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property; + +import org.eclipse.wb.internal.core.model.property.category.PropertyCategory; + + +/** + * {@link PropertyManager} is used to get/set attributes of {@link Property}. + * + * @author scheglov_ke + * @coverage core.model.property + */ +public final class PropertyManager { + public static PropertyCategory getCategory(Property property) { + // Note: In WindowBuilder there was a bunch of support for loading custom + // categories here based on toolkits; in ADT we'll need to do it differently + // so this code was all stripped out. + return property.getCategory(); + } + + /** + * @return the forced {@link PropertyCategory} of given Property, may be null. + */ + public static PropertyCategory getCategoryForced(Property property) { + return null; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategory.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategory.java new file mode 100644 index 0000000..a135b03 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategory.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.category; + +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.check.Assert; + +/** + * Describes category of {@link Property}. + * + * @author scheglov_ke + * @coverage core.model.property + */ +public final class PropertyCategory { + /** + * "Normal" category, used for properties that should be displayed without any effect. + */ + public static final PropertyCategory NORMAL = new PropertyCategory(0, "NORMAL"); + /** + * "Preferred" category, for properties that are most useful for component. + */ + public static final PropertyCategory PREFERRED = new PropertyCategory(-1, "PREFERRED"); + /** + * "Advanced" category, for properties that are rarely used, visible if modified, even if not + * enabled. + */ + public static final PropertyCategory ADVANCED = new PropertyCategory(1, "ADVANCED"); + /** + * "Advanced" category, for properties that are rarely used, visible only if enabled. + */ + public static final PropertyCategory ADVANCED_REALLY = new PropertyCategory(2, "ADVANCED_REALLY"); + /** + * "Hidden" category, for properties that should not be displayed. + */ + public static final PropertyCategory HIDDEN = new PropertyCategory(3, "HIDDEN"); + + //////////////////////////////////////////////////////////////////////////// + // + // System + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the system {@link PropertyCategory} with given priority. + */ + public static final PropertyCategory system(int priority) { + return new PropertyCategory(SYSTEM_BASE + priority, "SYSTEM:" + priority); + } + + /** + * @return the system {@link PropertyCategory} with priority + * system.getPriority() + additional. + */ + public static final PropertyCategory system(PropertyCategory system, int additional) { + Assert.isTrue(system.isSystem()); + return system(system.getPriority() - SYSTEM_BASE + additional); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Instance fields + // + //////////////////////////////////////////////////////////////////////////// + private static final int SYSTEM_BASE = 1000; + private final int m_priority; + private final String m_string; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + private PropertyCategory(int priority, String string) { + m_priority = priority; + m_string = string; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Object + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String toString() { + return m_string; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof PropertyCategory) { + PropertyCategory category = (PropertyCategory) obj; + return m_priority == category.m_priority; + } + // unknown class + return false; + } + + @Override + public int hashCode() { + return m_priority; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return true if this property is preferred. + */ + public boolean isPreferred() { + return this == PREFERRED; + } + + /** + * @return true if this property is advanced. + */ + public boolean isAdvanced() { + return this == ADVANCED; + } + + /** + * @return true if this property is really advanced. + */ + public boolean isAdvancedReally() { + return this == ADVANCED_REALLY; + } + + /** + * @return true if this property is hidden. + */ + public boolean isHidden() { + return this == HIDDEN; + } + + /** + * @return true if this property is system. + */ + public boolean isSystem() { + return m_priority >= 900; + } + + /** + * @return the priority of this category. + */ + public int getPriority() { + return m_priority; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProvider.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProvider.java new file mode 100644 index 0000000..b435576 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProvider.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.category; + +import org.eclipse.wb.internal.core.model.property.Property; + +/** + * This interface is used to get {@link PropertyCategory} for {@link Property}. + * + * @author scheglov_ke + * @coverage core.model.property + */ +public interface PropertyCategoryProvider { + /** + * @return the {@link PropertyCategory} of given Property, not null. + */ + PropertyCategory getCategory(Property property); +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProviders.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProviders.java new file mode 100644 index 0000000..83aaebb --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProviders.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.category; + +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.PropertyManager; + +/** + * Factory for {@link PropertyCategoryProvider} instances. + * + * @author scheglov_ke + * @coverage core.model.property + */ +public final class PropertyCategoryProviders { + //////////////////////////////////////////////////////////////////////////// + // + // Simple providers + // + //////////////////////////////////////////////////////////////////////////// + private static final PropertyCategoryProvider FROM_PROPERTY = new PropertyCategoryProvider() { + public PropertyCategory getCategory(Property property) { + return property.getCategory(); + } + }; + + /** + * Returns result of {@link Property#getCategory()}, never null. + */ + public static PropertyCategoryProvider fromProperty() { + return FROM_PROPERTY; + } + + private static final PropertyCategoryProvider FORCED_BY_USER = new PropertyCategoryProvider() { + public PropertyCategory getCategory(Property property) { + return PropertyManager.getCategoryForced(property); + } + }; + + /** + * Returns category forced by user, may be null. + */ + public static PropertyCategoryProvider forcedByUser() { + return FORCED_BY_USER; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Compound + // + //////////////////////////////////////////////////////////////////////////// + /** + * Returns first not null category returned by provider. + */ + public static PropertyCategoryProvider combine(final PropertyCategoryProvider... providers) { + return new PropertyCategoryProvider() { + public PropertyCategory getCategory(Property property) { + for (PropertyCategoryProvider provider : providers) { + PropertyCategory category = provider.getCategory(property); + if (category != null) { + return category; + } + } + throw new IllegalStateException("Can not provide category for " + property.getTitle()); + } + }; + } + + private static final PropertyCategoryProvider DEF = combine(forcedByUser(), fromProperty()); + + /** + * Returns the default combination of {@link PropertyCategoryProvider}s - first + * {@link #forcedByUser()}, then {@link #fromProperty()}. + */ + public static PropertyCategoryProvider def() { + return DEF; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboBoxPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboBoxPropertyEditor.java new file mode 100644 index 0000000..f122381 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboBoxPropertyEditor.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.core.controls.CCombo3; +import org.eclipse.wb.core.controls.CComboBox; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; +import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils; +import org.eclipse.wb.internal.core.utils.execution.RunnableEx; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; + +/** + * The {@link PropertyEditor} for selecting single value using {@link CComboBox}. This editor has + * in-line search-feature and is more suitable (vs {@link AbstractComboPropertyEditor}) for + * properties with large lists of value items. + * + * @author sablin_aa + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public abstract class AbstractComboBoxPropertyEditor extends TextDisplayPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + private CComboBox m_combo; + private String m_dropDelayedText; + + @Override + public final boolean activate(final PropertyTable propertyTable, + final Property property, + Point location) throws Exception { + m_combo = new CComboBox(propertyTable, SWT.NONE); + // initialize + addItems(property, m_combo); + selectItem(property, m_combo); + // install listeners + m_combo.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + handleKeyPressed(propertyTable, property, e); + } + }); + m_combo.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + propertyTable.deactivateEditor(true); + } + }); + m_combo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + propertyTable.deactivateEditor(true); + } + }); + m_combo.setFocus(); + // schedule showing drop-down, because we don't have bounds yet + ExecutionUtils.runAsync(new RunnableEx() { + public void run() throws Exception { + m_combo.comboDropDown(true); + if (m_dropDelayedText != null) { + m_combo.setEditText(m_dropDelayedText); + m_combo.setEditSelection(m_dropDelayedText.length(), m_dropDelayedText.length()); + m_dropDelayedText = null; + } + } + }); + // keep editor active + return true; + } + + private void handleKeyPressed(PropertyTable propertyTable, Property property, KeyEvent e) { + if (e.keyCode == SWT.ESC) { + propertyTable.deactivateEditor(false); + } else if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) { + e.doit = false; + propertyTable.deactivateEditor(true); + propertyTable.navigate(e); + } + } + + @Override + public final void deactivate(PropertyTable propertyTable, Property property, boolean save) { + if (save) { + toProperty(propertyTable, property); + } + if (m_combo != null) { + m_combo.dispose(); + m_combo = null; + } + } + + private void toProperty(PropertyTable propertyTable, Property property) { + try { + toPropertyEx(property, m_combo); + } catch (Throwable e) { + propertyTable.handleException(e); + } + } + + @Override + public void setBounds(Rectangle bounds) { + m_combo.setBounds(bounds); + } + + @Override + public void keyDown(PropertyTable propertyTable, Property property, KeyEvent event) + throws Exception { + boolean withAlt = (event.stateMask & SWT.ALT) != 0; + boolean withCtrl = (event.stateMask & SWT.CTRL) != 0; + if (event.character > 0x20 && !(withAlt || withCtrl)) { + propertyTable.activateEditor(property, null); + m_dropDelayedText = "" + event.character; + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Abstract methods + // + //////////////////////////////////////////////////////////////////////////// + /** + * Adds items to given {@link CComboBox}. + */ + protected abstract void addItems(Property property, CComboBox combo) throws Exception; + + /** + * Selects current item in given {@link CCombo3}. + */ + protected void selectItem(Property property, CComboBox combo) throws Exception { + } + + /** + * Transfers data from widget to {@link Property}. + */ + protected abstract void toPropertyEx(Property property, CComboBox combo) throws Exception; +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboPropertyEditor.java new file mode 100644 index 0000000..a225f45 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboPropertyEditor.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.core.controls.CCombo3; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; + +/** + * The {@link PropertyEditor} for selecting single value using {@link CCombo3}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public abstract class AbstractComboPropertyEditor extends TextDisplayPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + private CCombo3 m_combo; + private boolean m_doDropDown; + + @Override + public boolean activate(final PropertyTable propertyTable, final Property property, Point location) + throws Exception { + // create combo + { + m_combo = new CCombo3(propertyTable, SWT.NONE); + m_doDropDown = true; + // add items + addItems(property, m_combo); + // select item + selectItem(property, m_combo); + } + // add listeners + m_combo.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + propertyTable.deactivateEditor(true); + } + }); + m_combo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + int index = m_combo.getSelectionIndex(); + toProperty(propertyTable, property, index); + } + }); + m_combo.addListener(SWT.KeyDown, new Listener() { + public void handleEvent(Event event) { + switch (event.keyCode) { + case SWT.ESC : + propertyTable.deactivateEditor(false); + break; + case SWT.DEL : + try { + property.setValue(Property.UNKNOWN_VALUE); + event.doit = false; + selectItem(property, m_combo); + } catch (Throwable e) { + propertyTable.handleException(e); + propertyTable.deactivateEditor(false); + } + m_combo.doDropDown(false); + break; + } + } + }); + m_combo.addMouseListener(new MouseAdapter() { + @Override + public void mouseDoubleClick(MouseEvent e) { + int index = (m_combo.getSelectionIndex() + 1) % m_combo.getItemCount(); + toProperty(propertyTable, property, index); + } + }); + // keep editor active + return true; + } + + @Override + public final void setBounds(Rectangle bounds) { + m_combo.setBounds(bounds); + // editor created without bounds, so activate it after first setBounds() + if (m_doDropDown) { + m_doDropDown = false; + m_combo.setFocus(); + m_combo.doDropDown(true); + m_combo.startDrag(); + } + } + + @Override + public final void deactivate(PropertyTable propertyTable, Property property, boolean save) { + if (m_combo != null) { + m_combo.dispose(); + m_combo = null; + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Abstract methods + // + //////////////////////////////////////////////////////////////////////////// + /** + * Adds items to given {@link CCombo3}. + */ + protected abstract void addItems(Property property, CCombo3 combo) throws Exception; + + /** + * Selects current item in given {@link CCombo3}. + */ + protected abstract void selectItem(Property property, CCombo3 combo) throws Exception; + + /** + * Transfers data from widget to {@link Property}. + */ + protected abstract void toPropertyEx(Property property, CCombo3 combo, int index) + throws Exception; + + /** + * Transfers data from widget to {@link Property}. + */ + private void toProperty(PropertyTable propertyTable, Property property, int index) { + try { + toPropertyEx(property, m_combo, index); + } catch (Throwable e) { + propertyTable.handleException(e); + } + propertyTable.deactivateEditor(false); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractListPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractListPropertyEditor.java new file mode 100644 index 0000000..ba34103 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractListPropertyEditor.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.core.controls.CCombo3; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.check.Assert; + +import java.util.List; +import java.util.Map; + +/** + * The {@link PropertyEditor} for selecting single expression from given set. + * + * @author sablin_aa + * @coverage core.model.property.editor + */ +public abstract class AbstractListPropertyEditor extends AbstractComboPropertyEditor + implements + IValueSourcePropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // TextDisplayPropertyEditor + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + // return title for value + Object value = property.getValue(); + if (value != Property.UNKNOWN_VALUE) { + int index = getValueIndex(value); + if (index >= 0) { + return getTitle(index); + } else { + if (value instanceof String) { + return (String) value; + } + } + } + // unknown value + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // IValueSourcePropertyEditor + // + //////////////////////////////////////////////////////////////////////////// + @Override +public String getValueSource(Object value) throws Exception { + // return expression for value + if (value != Property.UNKNOWN_VALUE) { + int index = getValueIndex(value); + if (index >= 0) { + return getExpression(index); + } + } + // unknown value + return null; + } + +// //////////////////////////////////////////////////////////////////////////// +// // +// // IClipboardSourceProvider +// // +// //////////////////////////////////////////////////////////////////////////// +// @Override +//public String getClipboardSource(GenericProperty property) throws Exception { +// Object value = property.getValue(); +// return getValueSource(value); +// } + + //////////////////////////////////////////////////////////////////////////// + // + // Combo + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected void addItems(Property property, CCombo3 combo) throws Exception { + for (int i = 0; i < getCount(); i++) { + combo.add(getTitle(i)); + } + } + + @Override + protected void selectItem(Property property, CCombo3 combo) throws Exception { + combo.setText(getText(property)); + } + + @Override + protected void toPropertyEx(Property property, CCombo3 combo, int index) throws Exception { +// if (property instanceof GenericProperty) { +// GenericProperty genericProperty = (GenericProperty) property; +// String expression = getExpression(index); +// Object evaluatedExpression = evaluateExpression(genericProperty, expression); +// // apply expression +// genericProperty.setExpression(expression, evaluatedExpression); +// } else { + toPropertyEx_simpleProperty(property, combo, index); +// } + } + +// private static Object evaluateExpression(final GenericProperty genericProperty, +// final String expression) { +// return ExecutionUtils.runObjectIgnore(new RunnableObjectEx() { +// public Object runObject() throws Exception { +// JavaInfo javaInfo = genericProperty.getJavaInfo(); +// ClassLoader classLoader = JavaInfoUtils.getClassLoader(javaInfo); +// return ScriptUtils.evaluate(classLoader, expression); +// } +// }, Property.UNKNOWN_VALUE); +// System.out.println("HACK 1234"); +// return Property.UNKNOWN_VALUE; +// } + + /** + * Sets value of simple {@link Property}, not {@link GenericProperty}. + */ + protected void toPropertyEx_simpleProperty(Property property, CCombo3 combo, int index) + throws Exception { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access to list items + // + //////////////////////////////////////////////////////////////////////////// + abstract protected int getCount(); + + abstract protected int getValueIndex(Object value); + + abstract protected String getTitle(int index); + + abstract protected String getExpression(int index) throws Exception; + + //////////////////////////////////////////////////////////////////////////// + // + // Utils + // + //////////////////////////////////////////////////////////////////////////// + /** + * Extract string array from parameters. + */ + protected static String[] getParameterAsArray(Map parameters, String name) { + return getParameterAsArray(parameters, name, false); + } + + @SuppressWarnings("unchecked") + protected static String[] getParameterAsArray(Map parameters, + String name, + boolean noAssert) { + String[] values = null; + if (parameters.containsKey(name)) { + List list = (List) parameters.get(name); + values = list.toArray(new String[list.size()]); + } else { + if (noAssert) { + values = null; + } else { + Assert.fail(String.format("No parameter %s in %s.", name, parameters)); + } + } + return values; + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractTextPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractTextPropertyEditor.java new file mode 100644 index 0000000..1cf9574 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractTextPropertyEditor.java @@ -0,0 +1,306 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.jface.bindings.keys.KeyStroke; +import org.eclipse.jface.fieldassist.ContentProposalAdapter; +import org.eclipse.jface.fieldassist.IContentProposalProvider; +import org.eclipse.jface.fieldassist.IControlContentAdapter; +import org.eclipse.jface.fieldassist.TextContentAdapter; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; + +/** + * Abstract {@link PropertyEditor} for that uses {@link Text} as control. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public abstract class AbstractTextPropertyEditor extends TextDisplayPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + private Text m_textControl; + private boolean m_ignoreFocusLost; + + // BEGIN ADT MODIFICATIONS + // ContentProposalAdapter which exposes the openProposalPopup method such + // that we can open the dialog up immediately on focus gain to show all available + // alternatives (the default implementation requires at least one keytroke before + // it shows up) + private static class ImmediateProposalAdapter extends ContentProposalAdapter { + public ImmediateProposalAdapter(Text control, + IControlContentAdapter controlContentAdapter, + IContentProposalProvider proposalProvider, KeyStroke keyStroke, + char[] autoActivationCharacters) { + super(control, controlContentAdapter, proposalProvider, keyStroke, + autoActivationCharacters); + + // On focus gain, start completing + control.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent event) { + openIfNecessary(); + } + + @Override + public void focusLost(FocusEvent event) { + } + }); + + /* Triggering on empty is disabled for now: it has the unfortunate side-effect + that it's impossible to enter a blank text field - blank matches everything, + so the first item will automatically be selected when you press return. + + + // If you edit the text and delete everything, the normal implementation + // will close the popup; we'll reopen it + control.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent event) { + if (((Text) getControl()).getText().isEmpty()) { + openIfNecessary(); + } + } + }); + */ + } + + private void openIfNecessary() { + getControl().getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + if (!isProposalPopupOpen()) { + openProposalPopup(); + } + } + }); + } + } + // END ADT MODIFICATIONS + + @Override + public boolean activate(final PropertyTable propertyTable, final Property property, Point location) + throws Exception { + // create Text + { + m_textControl = new Text(propertyTable, SWT.NONE); + new TextControlActionsManager(m_textControl); + m_textControl.setEditable(isEditable()); + + // BEGIN ADT MODIFICATIONS + // Add support for field completion, if the property provides an IContentProposalProvider + // via its the getAdapter method. + IContentProposalProvider completion = property.getAdapter(IContentProposalProvider.class); + if (completion != null) { + ImmediateProposalAdapter adapter = new ImmediateProposalAdapter( + m_textControl, new TextContentAdapter(), completion, null, null); + adapter.setFilterStyle(ContentProposalAdapter.FILTER_NONE); + adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); + ILabelProvider labelProvider = property.getAdapter(ILabelProvider.class); + if (labelProvider != null) { + adapter.setLabelProvider(labelProvider); + } + } + // END ADT MODIFICATIONS + m_textControl.setFocus(); + } + // add listeners + m_textControl.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + try { + handleKeyPressed(propertyTable, property, e); + } catch (Throwable ex) { + propertyTable.deactivateEditor(false); + propertyTable.handleException(ex); + } + } + }); + m_textControl.addListener(SWT.FocusOut, new Listener() { + @Override + public void handleEvent(Event event) { + if (!m_ignoreFocusLost) { + propertyTable.deactivateEditor(true); + } + } + }); + // set data + toWidget(property); + // keep us active + return true; + } + + @Override + public final void setBounds(Rectangle bounds) { + m_textControl.setBounds(bounds); + } + + @Override + public final void deactivate(PropertyTable propertyTable, Property property, boolean save) { + if (save) { + try { + toProperty(property); + } catch (Throwable e) { + propertyTable.deactivateEditor(false); + propertyTable.handleException(e); + } + } + // dispose Text widget + if (m_textControl != null) { + m_textControl.dispose(); + m_textControl = null; + } + } + + @Override + public void keyDown(PropertyTable propertyTable, Property property, KeyEvent event) + throws Exception { + boolean withAlt = (event.stateMask & SWT.ALT) != 0; + boolean withCtrl = (event.stateMask & SWT.CTRL) != 0; + if (event.character != 0 && !(withAlt || withCtrl)) { + propertyTable.activateEditor(property, null); + postKeyEvent(SWT.KeyDown, event); + postKeyEvent(SWT.KeyUp, event); + } + } + + /** + * Posts low-level {@link SWT.KeyDown} or {@link SWT.KeyUp} event. + */ + private static void postKeyEvent(int type, KeyEvent event) { + Event lowEvent = new Event(); + lowEvent.type = type; + lowEvent.keyCode = event.keyCode; + lowEvent.character = event.character; + // post event + Display.getCurrent().post(lowEvent); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Events + // + //////////////////////////////////////////////////////////////////////////// + /** + * Handles {@link KeyListener#keyPressed(KeyEvent)}. + */ + private void handleKeyPressed(PropertyTable propertyTable, Property property, KeyEvent e) + throws Exception { + if (e.keyCode == SWT.CR) { + toProperty(property); + + // BEGIN ADT MODIFICATIONS + // After pressing return, dismiss the text cursor to make the value "committed". + // I'm not sure why this is necessary here and not in WindowBuilder proper. + propertyTable.deactivateEditor(true); + // END ADT MODIFICATIONS + } else if (e.keyCode == SWT.ESC) { + propertyTable.deactivateEditor(false); + } else if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) { + e.doit = false; + boolean success = toProperty(property); + // don't allow navigation if current text can not be transferred to property + if (!success) { + return; + } + // OK, deactivate and navigate + propertyTable.deactivateEditor(true); + propertyTable.navigate(e); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Implementation + // + //////////////////////////////////////////////////////////////////////////// + private String m_currentText; + + /** + * Transfers data from {@link Property} to widget. + */ + private void toWidget(Property property) throws Exception { + // prepare text + String text = getEditorText(property); + if (text == null) { + text = ""; + } + // set text + m_currentText = text; + m_textControl.setText(text); + m_textControl.selectAll(); + } + + /** + * Transfers data from widget to {@link Property}. + * + * @return true if transfer was successful. + */ + private boolean toProperty(Property property) throws Exception { + String text = m_textControl.getText(); + // change property only if text was changed + if (!m_currentText.equals(text)) { + m_ignoreFocusLost = true; + try { + boolean success = setEditorText(property, text); + if (!success) { + return false; + } + } finally { + m_ignoreFocusLost = false; + } + // if value was successfully changed, update current text + m_currentText = text; + } + // OK, success + return true; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Operations + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return true if this editor can modify text. + */ + protected boolean isEditable() { + return true; + } + + /** + * @return the text to display in {@link Text} control. + */ + protected abstract String getEditorText(Property property) throws Exception; + + /** + * Modifies {@link Property} using given text. + * + * @return true if {@link Property} was successfully modified. + */ + protected abstract boolean setEditorText(Property property, String text) throws Exception; +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanObjectPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanObjectPropertyEditor.java new file mode 100644 index 0000000..4f80bb9 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanObjectPropertyEditor.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; +import org.eclipse.wb.internal.core.utils.ui.DrawUtils; + +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; + +/** + * The {@link PropertyEditor} for Boolean. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class BooleanObjectPropertyEditor extends PropertyEditor { + private static final Image m_nullImage = DesignerPlugin.getImage("properties/BooleanNull.png"); + private static final Image m_trueImage = DesignerPlugin.getImage("properties/true.png"); + private static final Image m_falseImage = DesignerPlugin.getImage("properties/false.png"); + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final PropertyEditor INSTANCE = new BooleanObjectPropertyEditor(); + + private BooleanObjectPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public void paint(Property property, GC gc, int x, int y, int width, int height) throws Exception { + Object value = property.getValue(); + if (value instanceof Boolean) { + boolean booleanValue = ((Boolean) value).booleanValue(); + Image image = booleanValue ? m_trueImage : m_falseImage; + String text = Boolean.toString(booleanValue); + paint(gc, x, y, width, height, text, image); + } + if (value == null) { + Image image = m_nullImage; + String text = "null"; + paint(gc, x, y, width, height, text, image); + } + } + + private void paint(GC gc, int x, int y, int width, int height, String text, Image image) { + // draw image + { + DrawUtils.drawImageCV(gc, image, x, y, height); + // prepare new position/width + int imageWidth = image.getBounds().width + 2; + x += imageWidth; + width -= imageWidth; + } + // draw text + { + DrawUtils.drawStringCV(gc, text, x, y, width, height); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + public boolean activate(PropertyTable propertyTable, Property property, Point location) + throws Exception { + // check that user clicked on image + if (location == null || location.x < m_trueImage.getBounds().width + 2) { + invertValue(property); + } + // don't activate + return false; + } + + @Override + public void doubleClick(Property property, Point location) throws Exception { + invertValue(property); + } + + /** + * Inverts the value of given boolean {@link Property}. + */ + private void invertValue(Property property) throws Exception { + Object value = property.getValue(); + // null + if (value == null) { + property.setValue(true); + return; + } + // boolean + if (value instanceof Boolean) { + boolean booleanValue = ((Boolean) value).booleanValue(); + property.setValue(!booleanValue); + return; + } + // unknown + property.setValue(true); + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanPropertyEditor.java new file mode 100644 index 0000000..6639afe --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanPropertyEditor.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; +import org.eclipse.wb.internal.core.utils.ui.DrawUtils; + +/** + * The {@link PropertyEditor} for boolean. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class BooleanPropertyEditor extends PropertyEditor { + private static final Image m_trueImage = DesignerPlugin.getImage("properties/true.png"); + private static final Image m_falseImage = DesignerPlugin.getImage("properties/false.png"); + private static final Image m_unknownImage = + DesignerPlugin.getImage("properties/BooleanUnknown.png"); + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final PropertyEditor INSTANCE = new BooleanPropertyEditor(); + + private BooleanPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public void paint(Property property, GC gc, int x, int y, int width, int height) throws Exception { + Object value = property.getValue(); + if (value instanceof Boolean) { + boolean booleanValue = ((Boolean) value).booleanValue(); + Image image = booleanValue ? m_trueImage : m_falseImage; + String text = Boolean.toString(booleanValue); + paint(gc, x, y, width, height, image, text); + } else { + paint(gc, x, y, width, height, m_unknownImage, "unknown"); + } + } + + /** + * Paints {@link Image} and text. + */ + private void paint(GC gc, int x, int y, int width, int height, Image image, String text) { + // draw image + { + DrawUtils.drawImageCV(gc, image, x, y, height); + // prepare new position/width + int imageWidth = image.getBounds().width + 2; + x += imageWidth; + width -= imageWidth; + } + // draw text + DrawUtils.drawStringCV(gc, text, x, y, width, height); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + public boolean activate(PropertyTable propertyTable, Property property, Point location) + throws Exception { + // check that user clicked on image + if (location == null || location.x < m_trueImage.getBounds().width + 2) { + invertValue(property); + } + // don't activate + return false; + } + + @Override + public void doubleClick(Property property, Point location) throws Exception { + invertValue(property); + } + + /** + * Inverts the value of given boolean {@link Property}. + */ + private void invertValue(Property property) throws Exception { + // prepare current boolean value + boolean booleanValue = false; + { + Object value = property.getValue(); + if (value instanceof Boolean) { + booleanValue = ((Boolean) value).booleanValue(); + } + } + // set inverted value + property.setValue(!booleanValue); + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/CharacterPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/CharacterPropertyEditor.java new file mode 100644 index 0000000..7e6cfe8 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/CharacterPropertyEditor.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.UiUtils; + +import java.text.MessageFormat; + +/** + * The {@link PropertyEditor} for {@link String}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class CharacterPropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final PropertyEditor INSTANCE = new CharacterPropertyEditor(); + + private CharacterPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value instanceof Character) { + return String.valueOf(((Character) value).charValue()); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + // check for delete + if (text.length() == 0) { + property.setValue(Property.UNKNOWN_VALUE); + return true; + } + // only one character + if (text.length() > 1) { + UiUtils.openWarning( + DesignerPlugin.getShell(), + property.getTitle(), + MessageFormat.format(ModelMessages.CharacterPropertyEditor_notValid, text)); + return false; + } + // modify property + property.setValue(new Character(text.charAt(0))); + return true; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoubleObjectPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoubleObjectPropertyEditor.java new file mode 100644 index 0000000..bb7dfc5 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoubleObjectPropertyEditor.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.UiUtils; + +import java.text.MessageFormat; + +/** + * The {@link PropertyEditor} for {@link Double}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class DoubleObjectPropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final DoubleObjectPropertyEditor INSTANCE = new DoubleObjectPropertyEditor(); + + private DoubleObjectPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value == null) { + return "null"; + } + if (value instanceof Double) { + return value.toString(); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + text = text.trim(); + // check for delete + if (text.length() == 0) { + property.setValue(Property.UNKNOWN_VALUE); + return true; + } + // check for "null" + if (text.equals("null")) { + property.setValue(null); + return true; + } + // prepare value + Double value; + try { + value = Double.valueOf(text); + } catch (Throwable e) { + UiUtils.openWarning( + DesignerPlugin.getShell(), + property.getTitle(), + MessageFormat.format(ModelMessages.DoubleObjectPropertyEditor_notValidDouble, text)); + return false; + } + // modify property + property.setValue(value); + return true; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoublePropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoublePropertyEditor.java new file mode 100644 index 0000000..40b73bf --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoublePropertyEditor.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.UiUtils; + +import java.text.MessageFormat; + +/** + * The {@link PropertyEditor} for double. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class DoublePropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final PropertyEditor INSTANCE = new DoublePropertyEditor(); + + private DoublePropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value instanceof Number) { + double doubleValue = ((Number) value).doubleValue(); + return Double.toString(doubleValue); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + text = text.trim(); + // check for delete + if (text.length() == 0) { + property.setValue(Property.UNKNOWN_VALUE); + } + // prepare value + Double value; + try { + value = Double.valueOf(text); + } catch (Throwable e) { + UiUtils.openWarning( + DesignerPlugin.getShell(), + property.getTitle(), + MessageFormat.format(ModelMessages.DoublePropertyEditor_notValidDouble, text)); + return false; + } + // modify property + property.setValue(value); + return true; + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/EnumerationValuesPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/EnumerationValuesPropertyEditor.java new file mode 100644 index 0000000..a715df3 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/EnumerationValuesPropertyEditor.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import com.google.common.base.Objects; + +import org.eclipse.wb.core.controls.CCombo3; +import org.eclipse.wb.internal.core.model.property.Property; + +import java.beans.PropertyDescriptor; + +/** + * {@link PropertyEditor} for "enumerationValues" attribute of {@link PropertyDescriptor}. + *

+ * See http://javadude.com/articles/javabeanattributes.html for attributes. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public class EnumerationValuesPropertyEditor extends AbstractComboPropertyEditor + implements + IValueSourcePropertyEditor { + private final String[] m_names; + private final Object[] m_values; + private final String[] m_sources; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public EnumerationValuesPropertyEditor(Object attributeValue) { + Object[] enumElements = (Object[]) attributeValue; + int items = enumElements.length / 3; + m_names = new String[items]; + m_values = new Object[items]; + m_sources = new String[items]; + for (int i = 0; i < items; i++) { + m_names[i] = (String) enumElements[3 * i + 0]; + m_values[i] = enumElements[3 * i + 1]; + m_sources[i] = (String) enumElements[3 * i + 2]; + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // TextDisplayPropertyEditor + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + // return name for value + if (value != Property.UNKNOWN_VALUE) { + for (int i = 0; i < m_values.length; i++) { + if (Objects.equal(m_values[i], value)) { + return m_names[i]; + } + } + } + // unknown value + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // IValueSourcePropertyEditor + // + //////////////////////////////////////////////////////////////////////////// + @Override +public String getValueSource(Object value) throws Exception { + if (value != Property.UNKNOWN_VALUE) { + for (int i = 0; i < m_values.length; i++) { + if (Objects.equal(m_values[i], value)) { + return m_sources[i]; + } + } + } + // unknown value + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // IClipboardSourceProvider + // + //////////////////////////////////////////////////////////////////////////// +// public String getClipboardSource(GenericProperty property) throws Exception { +// Object value = property.getValue(); +// return getValueSource(value); +// } + + //////////////////////////////////////////////////////////////////////////// + // + // Combo + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected void addItems(Property property, CCombo3 combo) throws Exception { + for (String title : m_names) { + combo.add(title); + } + } + + @Override + protected void selectItem(Property property, CCombo3 combo) throws Exception { + combo.setText(getText(property)); + } + + @Override + protected void toPropertyEx(Property property, CCombo3 combo, int index) throws Exception { + Object value = m_values[index]; +// if (property instanceof GenericProperty) { +// GenericProperty genericProperty = (GenericProperty) property; +// String source = getValueSource(value); +// genericProperty.setExpression(source, value); +// } else { + property.setValue(value); +// } + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/FloatPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/FloatPropertyEditor.java new file mode 100644 index 0000000..d4b468e --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/FloatPropertyEditor.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.UiUtils; + +import java.text.MessageFormat; + +/** + * The {@link PropertyEditor} for float. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public class FloatPropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final PropertyEditor INSTANCE = new FloatPropertyEditor(); + + protected FloatPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value instanceof Float) { + return value.toString(); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + text = text.trim(); + // check for delete + if (text.length() == 0) { + property.setValue(Property.UNKNOWN_VALUE); + } + // prepare value + Float value; + try { + value = Float.valueOf(text); + } catch (Throwable e) { + UiUtils.openWarning( + DesignerPlugin.getShell(), + property.getTitle(), + MessageFormat.format(ModelMessages.FloatPropertyEditor_notValidFloat, text)); + return false; + } + // modify property + property.setValue(value); + return true; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ITextValuePropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ITextValuePropertyEditor.java new file mode 100644 index 0000000..ed38c04 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ITextValuePropertyEditor.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.model.property.Property; + +/** + * Extension of {@link PropertyEditor} that can set value using its text presentation. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public interface ITextValuePropertyEditor { + /** + * Sets value that corresponds given text. + */ + void setText(Property property, String text) throws Exception; +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IValueSourcePropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IValueSourcePropertyEditor.java new file mode 100644 index 0000000..2999bf1 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IValueSourcePropertyEditor.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +/** + * Extension for {@link PropertyEditor} that can be used to convert {@link Object} value into Java + * source. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public interface IValueSourcePropertyEditor { + /** + * @return the Java source for given value. + */ + String getValueSource(Object value) throws Exception; +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerObjectPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerObjectPropertyEditor.java new file mode 100644 index 0000000..f488dff --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerObjectPropertyEditor.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.UiUtils; + +import java.text.MessageFormat; + +/** + * The {@link PropertyEditor} for {@link Integer}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class IntegerObjectPropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final IntegerObjectPropertyEditor INSTANCE = new IntegerObjectPropertyEditor(); + + private IntegerObjectPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value == null) { + return "null"; + } + if (value instanceof Integer) { + return value.toString(); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + text = text.trim(); + // check for delete + if (text.length() == 0) { + property.setValue(Property.UNKNOWN_VALUE); + return true; + } + // check for "null" + if (text.equals("null")) { + property.setValue(null); + return true; + } + // prepare value + Integer value; + try { + value = Integer.valueOf(text); + } catch (Throwable e) { + UiUtils.openWarning( + DesignerPlugin.getShell(), + property.getTitle(), + MessageFormat.format(ModelMessages.IntegerObjectPropertyEditor_notValidInt, text)); + return false; + } + // modify property + property.setValue(value); + return true; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerPropertyEditor.java new file mode 100644 index 0000000..5be13da --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerPropertyEditor.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.UiUtils; + +import java.text.MessageFormat; + +/** + * The {@link PropertyEditor} for int. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class IntegerPropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final IntegerPropertyEditor INSTANCE = new IntegerPropertyEditor(); + + private IntegerPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value instanceof Integer) { + return value.toString(); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + text = text.trim(); + // check for delete + if (text.length() == 0) { + property.setValue(Property.UNKNOWN_VALUE); + return true; + } + // prepare value + Integer value; + try { + value = Integer.valueOf(text); + } catch (Throwable e) { + UiUtils.openWarning( + DesignerPlugin.getShell(), + property.getTitle(), + MessageFormat.format(ModelMessages.IntegerPropertyEditor_notValidInt, text)); + return false; + } + // modify property + property.setValue(value); + return true; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LocalePropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LocalePropertyEditor.java new file mode 100644 index 0000000..9a6563d --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LocalePropertyEditor.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.model.property.Property; + +import java.util.Locale; + +/** + * {@link PropertyEditor} for {@link Locale}. + * + * @author sablin_aa + * @coverage core.model.property.editor + */ +public final class LocalePropertyEditor extends TextDialogPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final PropertyEditor INSTANCE = new LocalePropertyEditor(); + + private LocalePropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value instanceof Locale) { + return ((Locale) value).getDisplayName(); + } + // unknown value + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected void openDialog(Property property) throws Exception { + Object value = property.getValue(); +// ChooseLocaleDialog localeDialog; +// if (value instanceof Locale) { +// localeDialog = new ChooseLocaleDialog(DesignerPlugin.getShell(), (Locale) value); +// } else { +// localeDialog = new ChooseLocaleDialog(DesignerPlugin.getShell(), null); +// } +// // open dialog +// if (localeDialog.open() == Window.OK) { +// property.setValue(localeDialog.getSelectedLocale().getLocale()); +// } + System.out.println("TODO: Custom locale chooser here"); + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongObjectPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongObjectPropertyEditor.java new file mode 100644 index 0000000..7a74ded --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongObjectPropertyEditor.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.UiUtils; + +import java.text.MessageFormat; + +/** + * The {@link PropertyEditor} for {@link Long}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class LongObjectPropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final LongObjectPropertyEditor INSTANCE = new LongObjectPropertyEditor(); + + private LongObjectPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value == null) { + return "null"; + } + if (value instanceof Long) { + return value.toString(); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + text = text.trim(); + // check for delete + if (text.length() == 0) { + property.setValue(Property.UNKNOWN_VALUE); + return true; + } + // check for "null" + if (text.equals("null")) { + property.setValue(null); + return true; + } + // prepare value + Long value; + try { + value = Long.valueOf(text); + } catch (Throwable e) { + UiUtils.openWarning( + DesignerPlugin.getShell(), + property.getTitle(), + MessageFormat.format(ModelMessages.LongObjectPropertyEditor_notValidLong, text)); + return false; + } + // modify property + property.setValue(value); + return true; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongPropertyEditor.java new file mode 100644 index 0000000..8c17f83 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongPropertyEditor.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.UiUtils; + +import java.text.MessageFormat; + +/** + * The {@link PropertyEditor} for long. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public class LongPropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final PropertyEditor INSTANCE = new LongPropertyEditor(); + + private LongPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value instanceof Long) { + return value.toString(); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + text = text.trim(); + // check for delete + if (text.length() == 0) { + property.setValue(Property.UNKNOWN_VALUE); + } + // prepare value + Long value; + try { + value = Long.valueOf(text); + } catch (Throwable e) { + UiUtils.openWarning( + DesignerPlugin.getShell(), + property.getTitle(), + MessageFormat.format(ModelMessages.LongPropertyEditor_notValidLong, text)); + return false; + } + // modify property + property.setValue(value); + return true; + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyDescriptorEditorProvider.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyDescriptorEditorProvider.java new file mode 100644 index 0000000..a60b698 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyDescriptorEditorProvider.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import java.beans.PropertyDescriptor; + +/** + * {@link PropertyEditorProvider} that creates editors based on {@link PropertyDescriptor} + * attributes, such as "enumerationValues". + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class PropertyDescriptorEditorProvider extends PropertyEditorProvider { + //////////////////////////////////////////////////////////////////////////// + // + // PropertyEditorProvider + // + //////////////////////////////////////////////////////////////////////////// + @Override + public PropertyEditor getEditorForPropertyDescriptor(PropertyDescriptor descriptor) + throws Exception { + { + Object attributeValue = descriptor.getValue("enumerationValues"); + if (isEnumerationProperty(descriptor)) { + return new EnumerationValuesPropertyEditor(attributeValue); + } + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Utils + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return true if given {@link PropertyDescriptor} has attribute "enumerationValues" + * with valid value structure. + */ + private static boolean isEnumerationProperty(PropertyDescriptor descriptor) { + Object attributeValue = descriptor.getValue("enumerationValues"); + // should be Object[] + if (!(attributeValue instanceof Object[])) { + return false; + } + Object[] enumElements = (Object[]) attributeValue; + // should be multiple 3 + if (enumElements.length % 3 != 0) { + return false; + } + // elements should be sequence of [String,Object,String] + for (int i = 0; i < enumElements.length; i++) { + Object element = enumElements[i]; + if (i % 3 == 0 && !(element instanceof String)) { + return false; + } + if (i % 3 == 2 && !(element instanceof String)) { + return false; + } + } + // OK + return true; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditor.java new file mode 100644 index 0000000..fd2fa8f --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditor.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; + +/** + * Abstract editor for {@link Property}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public abstract class PropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the instance of {@link PropertyEditorPresentation}. + */ + public PropertyEditorPresentation getPresentation() { + return null; + } + + /** + * Paints given {@link Property} given rectangle (x, y, width, height) of {@link GC}. + */ + public abstract void paint(Property property, GC gc, int x, int y, int width, int height) + throws Exception; + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + /** + * Activates editor for given {@link Property} at given place of {@link Composite}. Activation + * happens when user selects property in {@link PropertyTable}. {@link PropertyEditor} should + * create here any {@link Control}'s required to edit {@link Property}. + * + * If any exception happens, {@link PropertyEditor} will be deactivated. + * + * @param location + * the mouse location, if editor is activated using mouse click, or null if + * it is activated using keyboard. + * + * @return true if editor should be remembered as active for future + * {@link #setBounds(Rectangle)} and {@link #deactivate(boolean)} invocation. Some editors + * need such local activation (for example for String), some - not (for boolean). + */ + public boolean activate(PropertyTable propertyTable, Property property, Point location) + throws Exception { + return false; + } + + /** + * Sets the new bounds for editor's control. + */ + public void setBounds(Rectangle bounds) { + } + + /** + * Deactivates editor for current {@link Property}. {@link PropertyEditor} should dispose any + * {@link Control}'s created before in {@link #activate(PropertyTable, Property, Point)}. + * + * If any exception happened during activation, editor still should be able to deactivate + * correctly. + * + * @param save + * is true if property should save value to {@link Property}. + */ + public void deactivate(PropertyTable propertyTable, Property property, boolean save) { + } + + /** + * Handles double click on {@link Property} value in {@link PropertyTable}. + * + * @param location + * the mouse location, relative to editor + */ + public void doubleClick(Property property, Point location) throws Exception { + } + + /** + * Handles {@link SWT#KeyDown} event in {@link PropertyTable}. + */ + public void keyDown(PropertyTable propertyTable, Property property, KeyEvent event) + throws Exception { + } + + //////////////////////////////////////////////////////////////////////////// + // + // IAdaptable + // + //////////////////////////////////////////////////////////////////////////// + public T getAdapter(Class adapter) { + return null; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditorProvider.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditorProvider.java new file mode 100644 index 0000000..2d11cbc --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditorProvider.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import java.beans.PropertyDescriptor; + +/** + * Provider for creating {@link PropertyEditor}'s. + * + * @author lobas_av + * @coverage core.model.property.editor + */ +public class PropertyEditorProvider { + /** + * @return the {@link PropertyEditor} for given property type or null. + */ + public PropertyEditor getEditorForType(Class propertyType) throws Exception { + return null; + } + + /** + * @return the {@link PropertyEditor} for given {@link java.beans.PropertyEditor} editor type or + * null. + */ + public PropertyEditor getEditorForEditorType(Class editorType) throws Exception { + return null; + } + + /** + * @return the {@link PropertyEditor} for given {@link PropertyDescriptor} or null. + */ + public PropertyEditor getEditorForPropertyDescriptor(PropertyDescriptor descriptor) + throws Exception { + return null; + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortObjectPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortObjectPropertyEditor.java new file mode 100644 index 0000000..c1f8383 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortObjectPropertyEditor.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.UiUtils; + +import java.text.MessageFormat; + +/** + * The {@link PropertyEditor} for {@link Short}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class ShortObjectPropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final ShortObjectPropertyEditor INSTANCE = new ShortObjectPropertyEditor(); + + private ShortObjectPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value == null) { + return "null"; + } + if (value instanceof Short) { + return value.toString(); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + text = text.trim(); + // check for delete + if (text.length() == 0) { + property.setValue(Property.UNKNOWN_VALUE); + return true; + } + // check for "null" + if (text.equals("null")) { + property.setValue(null); + return true; + } + // prepare value + Short value; + try { + value = Short.valueOf(text); + } catch (Throwable e) { + UiUtils.openWarning( + DesignerPlugin.getShell(), + property.getTitle(), + MessageFormat.format(ModelMessages.ShortObjectPropertyEditor_notValidShort, text)); + return false; + } + // modify property + property.setValue(value); + return true; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortPropertyEditor.java new file mode 100644 index 0000000..dba61c9 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortPropertyEditor.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.UiUtils; + +import java.text.MessageFormat; + +/** + * The {@link PropertyEditor} for short. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public class ShortPropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final PropertyEditor INSTANCE = new ShortPropertyEditor(); + + private ShortPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value instanceof Short) { + return value.toString(); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + text = text.trim(); + // check for delete + if (text.length() == 0) { + property.setValue(Property.UNKNOWN_VALUE); + } + // prepare value + Short value; + try { + value = Short.valueOf(text); + } catch (Throwable e) { + UiUtils.openWarning( + DesignerPlugin.getShell(), + property.getTitle(), + MessageFormat.format(ModelMessages.ShortPropertyEditor_notValidShort, text)); + return false; + } + // modify property + property.setValue(value); + return true; + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringArrayPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringArrayPropertyEditor.java new file mode 100644 index 0000000..fd78e01 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringArrayPropertyEditor.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import com.google.common.base.Joiner; + +import org.eclipse.jface.window.Window; +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.dialogs.StringsDialog; + +/** + * {@link PropertyEditor} for array of {@link String}'s. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public final class StringArrayPropertyEditor extends TextDialogPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final PropertyEditor INSTANCE = new StringArrayPropertyEditor(); + + private StringArrayPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getText(Property property) throws Exception { + String[] items = getItems(property); + return "[" + Joiner.on(", ").join(items) + "]"; + } + + /** + * @return the items specified in value of given {@link Property}. + */ + private static String[] getItems(Property property) throws Exception { + Object value = property.getValue(); + if (value instanceof String[]) { + return (String[]) value; + } + // no items + return new String[0]; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected void openDialog(Property property) throws Exception { + StringsDialog itemsDialog = + new StringsDialog(DesignerPlugin.getShell(), + DesignerPlugin.getDefault(), + property.getTitle(), + ModelMessages.StringArrayPropertyEditor_itemsLabel, + ModelMessages.StringArrayPropertyEditor_hint); + itemsDialog.setItems(getItems(property)); + // open dialog + if (itemsDialog.open() == Window.OK) { + property.setValue(itemsDialog.getItems()); + } + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringComboPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringComboPropertyEditor.java new file mode 100644 index 0000000..1861475 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringComboPropertyEditor.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.core.controls.CCombo3; +import org.eclipse.wb.internal.core.model.property.Property; + +/** + * The {@link PropertyEditor} for selecting single {@link String} value from given array. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public class StringComboPropertyEditor extends AbstractComboPropertyEditor { + private final String[] m_items; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public StringComboPropertyEditor(String... items) { + m_items = items; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getText(Property property) throws Exception { + return (String) property.getValue(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // AbstractComboPropertyEditor + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected void addItems(Property property, CCombo3 combo) throws Exception { + for (String item : m_items) { + combo.add(item); + } + } + + @Override + protected void selectItem(Property property, CCombo3 combo) throws Exception { + combo.setText(getText(property)); + } + + @Override + protected void toPropertyEx(Property property, CCombo3 combo, int index) throws Exception { + property.setValue(m_items[index]); + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringListPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringListPropertyEditor.java new file mode 100644 index 0000000..1bb8cd8 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringListPropertyEditor.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.core.controls.CCombo3; +import org.eclipse.wb.internal.core.model.property.Property; + +/** + * The {@link PropertyEditor} for selecting single string from given set. + * + * @author sablin_aa + * @coverage core.model.property.editor + */ +public final class StringListPropertyEditor extends AbstractListPropertyEditor { + private boolean m_ignoreCase; + private String[] m_strings; + + //////////////////////////////////////////////////////////////////////////// + // + // Combo + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected void toPropertyEx_simpleProperty(Property property, CCombo3 combo, int index) + throws Exception { + property.setValue(m_strings[index]); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access to list items + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected int getCount() { + return m_strings.length; + } + + @Override + protected int getValueIndex(Object value) { + if (value instanceof String) { + String string = (String) value; + for (int i = 0; i < getCount(); i++) { + if (m_ignoreCase) { + if (string.equalsIgnoreCase(m_strings[i])) { + return i; + } + } else { + if (string.equals(m_strings[i])) { + return i; + } + } + } + } + return -1; + } + + @Override + protected String getTitle(int index) { + return m_strings[index]; + } + + @Override + protected String getExpression(int index) throws Exception { + //return StringConverter.INSTANCE.toJavaSource(null, m_strings[index]); + // HACK!! + System.out.println("HACK!"); + return m_strings[index]; + } +// +// //////////////////////////////////////////////////////////////////////////// +// // +// // IConfigurablePropertyObject +// // +// //////////////////////////////////////////////////////////////////////////// +// public void configure(EditorState state, Map parameters) throws Exception { +// m_strings = getParameterAsArray(parameters, "strings"); +// m_ignoreCase = "true".equals(parameters.get("ignoreCase")); +// } +// +// /** +// * Configures this editor externally. +// */ +// public void configure(String[] strings) { +// m_strings = strings; +// } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextControlActionsManager.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextControlActionsManager.java new file mode 100644 index 0000000..dc7ba74 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextControlActionsManager.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.utils.binding.editors.controls.DefaultControlActionsManager; + +import org.eclipse.swt.widgets.Text; + +/** + * Manager for installing/unistalling global handlers for {@link Text} actions commands. + * + * @author mitin_aa + * @author sablin_aa + * @coverage core.model.property.editor + */ +public final class TextControlActionsManager extends DefaultControlActionsManager { + private final Text m_text; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public TextControlActionsManager(final Text text) { + super(text); + m_text = text; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Handlers + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected void selectAllExecuted() { + m_text.selectAll(); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDialogPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDialogPropertyEditor.java new file mode 100644 index 0000000..885d4ef --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDialogPropertyEditor.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation; +import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; + +import org.eclipse.swt.graphics.Point; + +/** + * Abstract {@link PropertyEditor} that displays text and button to open dialog. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public abstract class TextDialogPropertyEditor extends TextDisplayPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + private final PropertyEditorPresentation m_presentation = new ButtonPropertyEditorPresentation() { + @Override + protected void onClick(PropertyTable propertyTable, Property property) throws Exception { + openDialog(property); + } + }; + + @Override + public final PropertyEditorPresentation getPresentation() { + return m_presentation; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + public boolean activate(PropertyTable propertyTable, Property property, Point location) + throws Exception { + // activate using keyboard + if (location == null) { + openDialog(property); + } + // don't activate + return false; + } + + /** + * Opens editing dialog. + */ + protected abstract void openDialog(Property property) throws Exception; +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDisplayPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDisplayPropertyEditor.java new file mode 100644 index 0000000..9cf3703 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDisplayPropertyEditor.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor; + +import org.eclipse.swt.graphics.GC; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; +import org.eclipse.wb.internal.core.model.property.table.PropertyTooltipProvider; +import org.eclipse.wb.internal.core.utils.ui.DrawUtils; + +/** + * Abstract {@link PropertyEditor} for displaying text as {@link Property} value in + * {@link PropertyTable}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public abstract class TextDisplayPropertyEditor extends PropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public void paint(Property property, GC gc, int x, int y, int width, int height) throws Exception { + String text = getText(property); + if (text != null) { + DrawUtils.drawStringCV(gc, text, x, y, width, height); + } + } + + /** + * @return the text for displaying value of given {@link Property} or null if value + * of {@link Property} is unknown. + */ + protected abstract String getText(Property property) throws Exception; + + //////////////////////////////////////////////////////////////////////////// + // + // IAdaptable + // + //////////////////////////////////////////////////////////////////////////// + @Override + public T getAdapter(Class adapter) { + // tooltip for value text + if (adapter == PropertyTooltipProvider.class) { + return adapter.cast(createPropertyTooltipProvider()); + } + return super.getAdapter(adapter); + } + + /** + * @return the {@link PropertyTooltipProvider} to display value tooltip. + */ + protected PropertyTooltipProvider createPropertyTooltipProvider() { + return null; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/complex/IComplexPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/complex/IComplexPropertyEditor.java new file mode 100644 index 0000000..0634cfe --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/complex/IComplexPropertyEditor.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor.complex; + +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor; + +/** + * Extension for {@link PropertyEditor} that specifies that it has sub-properties. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public interface IComplexPropertyEditor { + /** + * @return sub-properties of given complex property. + */ + Property[] getProperties(Property property) throws Exception; +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentation.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentation.java new file mode 100644 index 0000000..e33970d --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentation.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor.presentation; + +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.EnvironmentUtils; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Button; + +/** + * Implementation of {@link PropertyEditorPresentation} for displaying {@link Button}. + * + * @author scheglov_ke + * @author mitin_aa + * @coverage core.model.property.editor + */ +public abstract class ButtonPropertyEditorPresentation extends PropertyEditorPresentation { + private final int m_style; + private final ButtonPropertyEditorPresentationImpl m_impl; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructors + // + //////////////////////////////////////////////////////////////////////////// + public ButtonPropertyEditorPresentation() { + this(SWT.NONE); + } + + public ButtonPropertyEditorPresentation(int style) { + m_style = style; + m_impl = + EnvironmentUtils.IS_MAC + ? new ButtonPropertyEditorPresentationImplMac(this) + : new ButtonPropertyEditorPresentationImpl(this); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sets "selection" property of {@link Button}. + */ + public final void setSelection(PropertyTable propertyTable, Property property, boolean selected) { + m_impl.setSelection(propertyTable, property, selected); + } + + //////////////////////////////////////////////////////////////////////////// + // + // PropertyEditorPresentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public final int show(final PropertyTable propertyTable, + final Property property, + final int x, + final int y, + final int width, + final int height) { + return m_impl.show(propertyTable, property, x, y, width, height); + } + + @Override + public final void hide(PropertyTable propertyTable, Property property) { + m_impl.hide(propertyTable, property); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + final int getStyle() { + return m_style; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Implementation + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the {@link Image} to display on {@link Button}. + */ + protected Image getImage() { + return DesignerPlugin.getImage("properties/dots.gif"); + } + + /** + * @return the tooltip text to display for {@link Button}. + */ + protected String getTooltip() { + return null; + } + + /** + * Handles click on {@link Button}. + */ + protected abstract void onClick(PropertyTable propertyTable, Property property) throws Exception; +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImpl.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImpl.java new file mode 100644 index 0000000..209bf39 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImpl.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor.presentation; + +import com.google.common.collect.Maps; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; +import org.eclipse.wb.internal.core.utils.Pair; + +import java.util.Map; + +/** + * Internal implementation of {@link PropertyEditorPresentation} for displaying {@link Button}. + * + * @author scheglov_ke + * @author mitin_aa + * @coverage core.model.property.editor + */ +class ButtonPropertyEditorPresentationImpl extends PropertyEditorPresentation { + protected final PropertyToControlMap m_propertyToControl = new PropertyToControlMap(); + private final ButtonPropertyEditorPresentation m_presentation; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public ButtonPropertyEditorPresentationImpl(ButtonPropertyEditorPresentation presentation) { + m_presentation = presentation; + } + + //////////////////////////////////////////////////////////////////////////// + // + // PropertyEditorPresentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public final void hide(PropertyTable propertyTable, Property property) { + Control control = m_propertyToControl.remove(propertyTable, property); + if (control != null) { + control.dispose(); + } + } + + @Override + public final int show(PropertyTable propertyTable, + Property property, + int x, + int y, + int width, + int height) { + // prepare control + Control control = m_propertyToControl.get(propertyTable, property); + if (control == null) { + control = createControl(propertyTable, property); + } + // set bounds + final int controlWidth = height; + final int controlX = x + width - controlWidth; + setBounds(control, controlX, y, controlWidth, height); + return controlWidth; + } + + /** + * Finds and select the appropriate {@link Control} belonging to given property. + */ + public void setSelection(PropertyTable propertyTable, Property property, boolean selected) { + Button button = (Button) m_propertyToControl.get(propertyTable, property); + if (button != null) { + button.setSelection(selected); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Control + // + //////////////////////////////////////////////////////////////////////////// + /** + * Creates the control for given property and initializes newly created control. + */ + private Control createControl(final PropertyTable propertyTable, final Property property) { + Control control = createControlImpl(propertyTable, property); + m_propertyToControl.put(propertyTable, property, control); + // when Control disposed, remove Control/Property from map to avoid memory leak + control.addListener(SWT.Dispose, new Listener() { + @Override + public void handleEvent(Event e) { + m_propertyToControl.remove(propertyTable, property); + } + }); + // activate property on mouse down + control.addListener(SWT.MouseDown, new Listener() { + @Override + public void handleEvent(Event event) { + propertyTable.deactivateEditor(true); + propertyTable.setActiveProperty(property); + } + }); + // return focus on propertyTable after click + control.addListener(SWT.MouseUp, new Listener() { + @Override + public void handleEvent(Event event) { + propertyTable.forceFocus(); + } + }); + // handle selection + control.addListener(SWT.Selection, new Listener() { + @Override + public void handleEvent(Event event) { + try { + getPresentation().onClick(propertyTable, property); + } catch (Throwable e) { + propertyTable.deactivateEditor(false); + propertyTable.handleException(e); + } + } + }); + return control; + } + + /** + * Creates the {@link Control} instance. By default, {@link Button} instance created. + */ + protected Control createControlImpl(final PropertyTable propertyTable, final Property property) { + Button button = new Button(propertyTable, getPresentation().getStyle()); + button.setImage(getPresentation().getImage()); + button.setToolTipText(getPresentation().getTooltip()); + return button; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the 'parent' presentation. Internal usage only. + */ + protected final ButtonPropertyEditorPresentation getPresentation() { + return m_presentation; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Utils + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sets new bounds for {@link Control}, optimizing when possible. + */ + private static void setBounds(Control control, int newX, int newY, int newWidth, int newHeight) { + // check, may be Control is invisible, so no reason to change bounds + { + // is in negative zone + if (newY + newHeight < 0) { + control.setVisible(false); + return; + } + // is out of client area height + Rectangle parentArea = control.getParent().getClientArea(); + if (newY > parentArea.height) { + control.setVisible(false); + return; + } + } + // well, now we sure that Control is visible + if (!control.getVisible()) { + control.setVisible(true); + } + // prepare old size, remember new + Integer oldWidthObject = (Integer) control.getData("oldWidth"); + Integer oldHeightObject = (Integer) control.getData("oldHeight"); + control.setData("oldWidth", newWidth); + control.setData("oldHeight", newHeight); + // check, may be same size + if (oldWidthObject != null) { + int oldWidth = oldWidthObject.intValue(); + int oldHeight = oldHeightObject.intValue(); + if (oldWidth == newWidth && oldHeight == newHeight) { + control.setLocation(newX, newY); + return; + } + } + // no any optimization possible, just set bounds + control.setBounds(newX, newY, newWidth, newHeight); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Controls map + // + //////////////////////////////////////////////////////////////////////////// + protected static final class PropertyToControlMap { + private final Map, Control> m_map = Maps.newHashMap(); + + void put(PropertyTable propertyTable, Property property, Control control) { + m_map.put(Pair.create(propertyTable, property), control); + } + + Control remove(PropertyTable propertyTable, Property property) { + return m_map.remove(Pair.create(propertyTable, property)); + } + + Control get(PropertyTable propertyTable, Property property) { + return m_map.get(Pair.create(propertyTable, property)); + } + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImplMac.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImplMac.java new file mode 100644 index 0000000..d61a606 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImplMac.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor.presentation; + +import org.eclipse.wb.core.controls.CFlatButton; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Control; + +/** + * Internal implementation of {@link PropertyEditorPresentation} for displaying special owner-draw + * button for Mac OSX. + * + * @author mitin_aa + * @coverage core.model.property.editor + */ +final class ButtonPropertyEditorPresentationImplMac extends ButtonPropertyEditorPresentationImpl { + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public ButtonPropertyEditorPresentationImplMac(ButtonPropertyEditorPresentation presentation) { + super(presentation); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Control + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected final Control createControlImpl(final PropertyTable propertyTable, Property property) { + CFlatButton button = new CFlatButton(propertyTable, SWT.NONE); + button.setImage(getPresentation().getImage()); + button.setToolTipText(getPresentation().getTooltip()); + return button; + } + + @Override + public final void setSelection(PropertyTable propertyTable, Property property, boolean selected) { + CFlatButton button = (CFlatButton) m_propertyToControl.get(propertyTable, property); + if (button != null) { + button.setSelected(selected); + } + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/CompoundPropertyEditorPresentation.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/CompoundPropertyEditorPresentation.java new file mode 100644 index 0000000..39bfa7a --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/CompoundPropertyEditorPresentation.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor.presentation; + +import com.google.common.collect.Lists; + +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; + +import java.util.List; + +/** + * Implementation of {@link PropertyEditorPresentation} that contains zero or more other + * {@link PropertyEditorPresentation}'s. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public class CompoundPropertyEditorPresentation extends PropertyEditorPresentation { + private final List m_presentations = Lists.newArrayList(); + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + /** + * Adds child {@link PropertyEditorPresentation}.
+ * Child {@link PropertyEditorPresentation}'s are displayed from right to left. + */ + public void add(PropertyEditorPresentation presentation) { + m_presentations.add(presentation); + } + + //////////////////////////////////////////////////////////////////////////// + // + // PropertyEditorPresentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public int show(PropertyTable propertyTable, + Property property, + int x, + int y, + int width, + int height) { + int sumWidth = 0; + for (PropertyEditorPresentation presentation : m_presentations) { + int presentationWidth = presentation.show(propertyTable, property, x, y, width, height); + sumWidth += presentationWidth; + width -= presentationWidth; + } + return sumWidth; + } + + @Override + public void hide(PropertyTable propertyTable, Property property) { + for (PropertyEditorPresentation presentation : m_presentations) { + presentation.hide(propertyTable, property); + } + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/PropertyEditorPresentation.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/PropertyEditorPresentation.java new file mode 100644 index 0000000..843338e --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/PropertyEditorPresentation.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor.presentation; + +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; + +/** + * Implementations of {@link PropertyEditorPresentation} are used to show some presentation for + * visible, but not activated yet {@link PropertyEditor}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public abstract class PropertyEditorPresentation { + /** + * Shows presentation for given {@link Property}. + * + * @return the width that this presentation occupies on the right of given rectangle. + */ + public abstract int show(PropertyTable propertyTable, + Property property, + int x, + int y, + int width, + int height); + + /** + * Hides presentation. + */ + public abstract void hide(PropertyTable propertyTable, Property property); +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyDialog.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyDialog.java new file mode 100644 index 0000000..2b0d47e --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyDialog.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor.string; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.ModelMessages; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils; +import org.eclipse.wb.internal.core.utils.execution.RunnableEx; +import org.eclipse.wb.internal.core.utils.ui.GridDataFactory; +import org.eclipse.wb.internal.core.utils.ui.dialogs.ResizableDialog; + +/** + * {@link Dialog} for editing value in {@link StringPropertyEditor}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public class StringPropertyDialog extends ResizableDialog { + // NOTE: In WindowBuilder this class had a lot of support for + // editing Java strings, dealing with automatic localization + // etc. This will need to be done differently in ADT (and had hooks + // into a bunch of other parts of WindowBuilder we're not including) + // so this was all stripped down to a plain String editor. + + //////////////////////////////////////////////////////////////////////////// + // + // Final fields + // + //////////////////////////////////////////////////////////////////////////// + private final Property m_property; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public StringPropertyDialog(Shell parentShell, Property property) throws Exception { + super(parentShell, DesignerPlugin.getDefault()); + m_property = property; + } + + //////////////////////////////////////////////////////////////////////////// + // + // GUI + // + //////////////////////////////////////////////////////////////////////////// + private Text m_valueText; + + @Override + public void create() { + super.create(); + m_valueText.selectAll(); + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite area = (Composite) super.createDialogArea(parent); + // value + { + // BEGIN ADT MODIFICATIONS + if (isMultiLine()) { + // END ADT MODIFICATIONS + m_valueText = new Text(area, SWT.BORDER | SWT.MULTI | SWT.WRAP); + GridDataFactory.create(m_valueText).grab().hintC(80, 8).fill(); + // BEGIN ADT MODIFICATIONS + } else { + m_valueText = new Text(area, SWT.BORDER | SWT.SINGLE); + GridDataFactory.create(m_valueText).grab().hintC(50, 1).fill(); + } + // END ADT MODIFICATIONS + // initial value + ExecutionUtils.runLog(new RunnableEx() { + @Override + public void run() throws Exception { + Object value = m_property.getValue(); + if (value instanceof String) { + m_valueText.setText((String) value); + } + } + }); + // handle Ctrl+Enter as OK + m_valueText.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.stateMask == SWT.CTRL && e.keyCode == SWT.CR) { + okPressed(); + } + } + }); + } + + return area; + } + + // BEGIN ADT MODIFICATIONS + protected boolean isMultiLine() { + return true; + } + // END ADT MODIFICATIONS + + //////////////////////////////////////////////////////////////////////////// + // + // Shell + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText(ModelMessages.StringPropertyDialog_title); + } + + @Override + protected void okPressed() { + final String value = m_valueText.getText(); + ExecutionUtils.runLog(new RunnableEx() { + @Override + public void run() throws Exception { + m_property.setValue(value); + } + }); + // close dialog + super.okPressed(); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyEditor.java new file mode 100644 index 0000000..0202fe5 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyEditor.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.editor.string; + +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.editor.AbstractTextPropertyEditor; +import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor; +import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation; +import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation; +import org.eclipse.wb.internal.core.model.property.table.PropertyTable; + +import org.eclipse.jface.window.Window; + +/** + * The {@link PropertyEditor} for {@link String}. + * + * @author scheglov_ke + * @coverage core.model.property.editor + */ +public class StringPropertyEditor extends AbstractTextPropertyEditor { + //////////////////////////////////////////////////////////////////////////// + // + // Instance + // + //////////////////////////////////////////////////////////////////////////// + public static final PropertyEditor INSTANCE = new StringPropertyEditor(); + + private StringPropertyEditor() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + private final PropertyEditorPresentation m_presentation = new ButtonPropertyEditorPresentation() { + @Override + protected void onClick(PropertyTable propertyTable, Property property) throws Exception { + openDialog(propertyTable, property); + } + }; + + @Override + public PropertyEditorPresentation getPresentation() { + return m_presentation; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Presentation + // + //////////////////////////////////////////////////////////////////////////// + @Override + public String getText(Property property) throws Exception { + Object value = property.getValue(); + if (value instanceof String) { + return (String) value; + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected String getEditorText(Property property) throws Exception { + return getText(property); + } + + @Override + protected boolean setEditorText(Property property, String text) throws Exception { + property.setValue(text); + return true; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editing in dialog + // + //////////////////////////////////////////////////////////////////////////// + /** + * Opens editing dialog. + */ + private void openDialog(PropertyTable propertyTable, Property property) throws Exception { + StringPropertyDialog dialog = new StringPropertyDialog(propertyTable.getShell(), property); + if (dialog.open() == Window.OK) { + } + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java new file mode 100644 index 0000000..5e05549 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java @@ -0,0 +1,332 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.table; + +import com.google.common.base.Charsets; +import com.google.common.base.Joiner; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.browser.LocationAdapter; +import org.eclipse.swt.browser.LocationEvent; +import org.eclipse.swt.browser.ProgressAdapter; +import org.eclipse.swt.browser.ProgressEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.browser.IWebBrowser; +import org.eclipse.ui.browser.IWorkbenchBrowserSupport; +import org.eclipse.wb.draw2d.IColorConstants; +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.EnvironmentUtils; +import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils; +import org.eclipse.wb.internal.core.utils.ui.GridDataFactory; +import org.eclipse.wb.internal.core.utils.ui.PixelConverter; + +import java.io.StringReader; +import java.net.URL; +import java.text.MessageFormat; + +/** + * Helper for displaying HTML tooltips. + * + * @author scheglov_ke + * @coverage core.model.property.table + */ +public final class HtmlTooltipHelper { + public static Control createTooltipControl(Composite parent, String header, String details) { + return createTooltipControl(parent, header, details, 0); + } + + public static Control createTooltipControl(Composite parent, + String header, + String details, + int heightLimit) { + // prepare Control + Control control; + try { + String html = ""; + if (header != null) { + html += ""; + } + html += ""; + html += "
" + header + "
" + details + "
"; + control = createTooltipControl_Browser(parent, html, heightLimit); + } catch (Throwable e) { + control = createTooltipControl_Label(parent, details); + } + // set listeners + { + Listener listener = new Listener() { + @Override + public void handleEvent(Event event) { + Control tooltipControl = (Control) event.widget; + hideTooltip(tooltipControl); + } + }; + control.addListener(SWT.MouseExit, listener); + } + // done + return control; + } + + /** + * Creates {@link Browser} for displaying tooltip. + */ + private static Control createTooltipControl_Browser(Composite parent, + String html, + final int heightLimitChars) { + // prepare styles + String styles; + try { + styles = DesignerPlugin.readFile(PropertyTable.class.getResourceAsStream("Tooltip.css"), + Charsets.US_ASCII); + if (styles == null) { + styles = ""; + } + } catch (Throwable e) { + styles = ""; + } + // prepare HTML with styles and tags + String wrappedHtml; + { + String bodyAttributes = + MessageFormat.format( + "text=''{0}'' bgcolor=''{1}''", + getColorWebString(IColorConstants.tooltipForeground), + getColorWebString(IColorConstants.tooltipBackground)); + String closeElement = + EnvironmentUtils.IS_LINUX + ? " Close" + : ""; + wrappedHtml = + /*CodeUtils.*/getSource( + "", + " ", + " ", + closeElement, + html, + " ", + ""); + } + // prepare Browser + final Browser browser = new Browser(parent, SWT.NONE); + browser.setText(wrappedHtml); + // open URLs in new window + browser.addLocationListener(new LocationAdapter() { + @Override + public void changing(LocationEvent event) { + event.doit = false; + hideTooltip((Browser) event.widget); + if (!"about:blank".equals(event.location)) { + try { + IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport(); + IWebBrowser browserSupport = support.createBrowser("wbp.browser"); + browserSupport.openURL(new URL(event.location)); + } catch (Throwable e) { + DesignerPlugin.log(e); + } + } + } + }); + // set size + { + int textLength = getTextLength(html); + // horizontal hint + int hintH = 50; + if (textLength < 100) { + hintH = 40; + } + // vertical hint + int hintV = textLength / hintH + 3; + hintV = Math.min(hintV, 8); + // do set + GridDataFactory.create(browser).hintC(hintH, hintV); + } + // tweak size after rendering HTML + browser.addProgressListener(new ProgressAdapter() { + @Override + public void completed(ProgressEvent event) { + browser.removeProgressListener(this); + tweakBrowserSize(browser, heightLimitChars); + browser.getShell().setVisible(true); + } + }); + // done + return browser; + } + + private static void tweakBrowserSize(Browser browser, int heightLimitChars) { + GridDataFactory.create(browser).grab().fill(); + // limit height + if (heightLimitChars != 0) { + PixelConverter pixelConverter = new PixelConverter(browser); + int maxHeight = pixelConverter.convertHeightInCharsToPixels(heightLimitChars); + expandShellToShowFullPage_Height(browser, maxHeight); + } + // if no limit, then show all, so make as tall as required + if (heightLimitChars == 0) { + expandShellToShowFullPage_Height(browser, Integer.MAX_VALUE); + } + } + + private static void expandShellToShowFullPage_Height(Browser browser, int maxHeight) { + try { + Shell shell = browser.getShell(); + // calculate required + int contentHeight; + { + getContentOffsetHeight(browser); + contentHeight = getContentScrollHeight(browser); + } + // apply height + int useHeight = Math.min(contentHeight + ((EnvironmentUtils.IS_LINUX) ? 2 : 10), maxHeight); + shell.setSize(shell.getSize().x, useHeight); + // trim height to content + { + int offsetHeight = getBodyOffsetHeight(browser); + int scrollHeight = getBodyScrollHeight(browser); + int delta = scrollHeight - offsetHeight; + if (delta != 0 && delta < 10) { + Point size = shell.getSize(); + shell.setSize(size.x, size.y + delta + 1); + } + } + // trim width to content + { + int offsetWidth = getContentOffsetWidth(browser); + { + Point size = shell.getSize(); + shell.setSize(offsetWidth + ((EnvironmentUtils.IS_MAC) ? 6 : 10), size.y); + } + } + // hide 'Close' if too narrow + if (EnvironmentUtils.IS_LINUX) { + if (shell.getSize().y < 30) { + hideCloseElement(browser); + } + } + } catch (Throwable e) { + } + } + + private static int getContentOffsetWidth(Browser browser) throws Exception { + return evaluateScriptInt( + browser, + "return document.getElementById('_wbp_tooltip_body').offsetWidth;"); + } + + private static int getContentOffsetHeight(Browser browser) throws Exception { + return evaluateScriptInt( + browser, + "return document.getElementById('_wbp_tooltip_body').offsetHeight;"); + } + + private static int getContentScrollHeight(Browser browser) throws Exception { + return evaluateScriptInt( + browser, + "return document.getElementById('_wbp_tooltip_body').scrollHeight;"); + } + + private static int getBodyOffsetHeight(Browser browser) throws Exception { + return evaluateScriptInt(browser, "return document.body.offsetHeight;"); + } + + private static int getBodyScrollHeight(Browser browser) throws Exception { + return evaluateScriptInt(browser, "return document.body.scrollHeight;"); + } + + private static int evaluateScriptInt(Browser browser, String script) throws Exception { + Object o = ReflectionUtils.invokeMethod(browser, "evaluate(java.lang.String)", script); + return ((Number) o).intValue(); + } + + private static void hideCloseElement(Browser browser) throws Exception { + String script = "document.getElementById('_wbp_close').style.display = 'none'"; + ReflectionUtils.invokeMethod(browser, "evaluate(java.lang.String)", script); + } + + /** + * @return the length of text in given HTML. Uses internal class, so may fail, in this case + * returns length on HTML. + */ + private static int getTextLength(String html) { + StringReader htmlStringReader = new StringReader(html); + try { + ClassLoader classLoader = PropertyTable.class.getClassLoader(); + Class readerClass = + classLoader.loadClass("org.eclipse.jface.internal.text.html.HTML2TextReader"); + Object reader = readerClass.getConstructors()[0].newInstance(htmlStringReader, null); + String text = (String) ReflectionUtils.invokeMethod(reader, "getString()"); + return text.length(); + } catch (Throwable e) { + return html.length(); + } + } + + /** + * Returns a string representation of {@link Color} suitable for web pages. + * + * @param color + * the {@link Color} instance, not null. + * @return a string representation of {@link Color} suitable for web pages. + */ + private static String getColorWebString(final Color color) { + String colorString = "#" + Integer.toHexString(color.getRed()); + colorString += Integer.toHexString(color.getGreen()); + colorString += Integer.toHexString(color.getBlue()); + return colorString; + } + + /** + * Creates {@link Label} if {@link Browser} can not be used. + */ + private static Control createTooltipControl_Label(Composite parent, String html) { + // prepare Label + final Label label = new Label(parent, SWT.WRAP); + label.setText(html); + // set size + int requiredWidth = label.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; + GridDataFactory.create(label).hintHC(50).hintHMin(requiredWidth); + // copy colors + label.setForeground(parent.getForeground()); + label.setBackground(parent.getBackground()); + // done + parent.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + Shell shell = label.getShell(); + shell.setVisible(true); + } + }); + return label; + } + + private static void hideTooltip(Control tooltip) { + tooltip.getShell().dispose(); + } + + // Copied from CodeUtils.java: CodeUtils.getSource() + /** + * @return the source as single {@link String}, lines joined using "\n". + */ + public static String getSource(String... lines) { + return Joiner.on('\n').join(lines); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyExceptionHandler.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyExceptionHandler.java new file mode 100644 index 0000000..879f699 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyExceptionHandler.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.table; + +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor; + +/** + * Handler for any {@link Exception} that happens in {@link PropertyTable}, i.e. exceptions during + * {@link Property} modifications using {@link PropertyEditor}'s. + * + * @author scheglov_ke + * @coverage core.model.property.table + */ +public interface IPropertyExceptionHandler { + void handle(Throwable e); +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyTooltipSite.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyTooltipSite.java new file mode 100644 index 0000000..e2fee14 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyTooltipSite.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.table; + +/** + * Interface that allows control of {@link PropertyTooltipProvider} interact with + * {@link PropertyTableTooltipHelper}. + * + * @author scheglov_ke + * @coverage core.model.property.table + */ +public interface IPropertyTooltipSite { + /** + * @return the {@link PropertyTable} of this site. + */ + PropertyTable getTable(); + + /** + * Hides current tooltip. + */ + void hideTooltip(); +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java new file mode 100644 index 0000000..7a49cb3 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java @@ -0,0 +1,1602 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.table; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.wb.draw2d.IColorConstants; +import org.eclipse.wb.draw2d.ICursorConstants; +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.model.property.category.PropertyCategory; +import org.eclipse.wb.internal.core.model.property.category.PropertyCategoryProvider; +import org.eclipse.wb.internal.core.model.property.category.PropertyCategoryProviders; +import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor; +import org.eclipse.wb.internal.core.model.property.editor.complex.IComplexPropertyEditor; +import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation; +import org.eclipse.wb.internal.core.utils.check.Assert; +import org.eclipse.wb.internal.core.utils.ui.DrawUtils; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * Control that can display {@link Property}'s and edit them using {@link PropertyEditor}'s. + * + * @author scheglov_ke + * @author lobas_av + * @coverage core.model.property.table + */ +public class PropertyTable extends Canvas implements ISelectionProvider { + //////////////////////////////////////////////////////////////////////////// + // + // Colors + // + //////////////////////////////////////////////////////////////////////////// + private static final Color COLOR_BACKGROUND = IColorConstants.listBackground; + private static final Color COLOR_NO_PROPERTIES = IColorConstants.gray; + private static final Color COLOR_LINE = IColorConstants.lightGray; + private static final Color COLOR_COMPLEX_LINE = DrawUtils.getShiftedColor( + IColorConstants.lightGray, + -32); + private static final Color COLOR_PROPERTY_BG = DrawUtils.getShiftedColor(COLOR_BACKGROUND, -12); + private static final Color COLOR_PROPERTY_BG_MODIFIED = COLOR_BACKGROUND; + private static final Color COLOR_PROPERTY_FG_TITLE = IColorConstants.listForeground; + private static final Color COLOR_PROPERTY_FG_VALUE = + DrawUtils.isDarkColor(IColorConstants.listBackground) + ? IColorConstants.lightBlue + : IColorConstants.darkBlue; + private static final Color COLOR_PROPERTY_BG_SELECTED = IColorConstants.listSelection; + private static final Color COLOR_PROPERTY_FG_SELECTED = IColorConstants.listSelectionText; + private static final Color COLOR_PROPERTY_FG_ADVANCED = IColorConstants.gray; + // BEGIN ADT MODIFICATIONS + public static final Color COLOR_PROPERTY_FG_DEFAULT = IColorConstants.darkGray; + // END ADT MODIFICATIONS + //////////////////////////////////////////////////////////////////////////// + // + // Sizes + // + //////////////////////////////////////////////////////////////////////////// + private static final int MIN_COLUMN_WIDTH = 75; + private static final int MARGIN_LEFT = 2; + private static final int MARGIN_RIGHT = 1; + private static final int STATE_IMAGE_MARGIN_RIGHT = 4; + //////////////////////////////////////////////////////////////////////////// + // + // Images + // + //////////////////////////////////////////////////////////////////////////// + private static final Image m_plusImage = DesignerPlugin.getImage("properties/plus.gif"); + private static final Image m_minusImage = DesignerPlugin.getImage("properties/minus.gif"); + private static int m_stateWidth = 9; + //////////////////////////////////////////////////////////////////////////// + // + // Instance fields + // + //////////////////////////////////////////////////////////////////////////// + private final PropertyTableTooltipHelper m_tooltipHelper; + private boolean m_showAdvancedProperties; + private Property[] m_rawProperties; + private List m_properties; + private final Set m_expandedIds = Sets.newTreeSet(); + // BEGIN ADT MODIFICATIONS + // Add support for "expand by default" : If you specify ids to be collapsed by + // default, then any *other* ids will be expanded by default. + private Set m_collapsedIds = null; + + /** + * Sets a set of ids that should be collapsed by default. Everything else + * will be expanded by default. If this method is not called, ids are + * collapsed by default instead. + * + * @param names set of ids to be collapsed + */ + public void setDefaultCollapsedNames(Collection names) { + m_collapsedIds = Sets.newTreeSet(); + for (String name : names) { + m_collapsedIds.add("|" + name); // See PropertyInfo constructor for id syntax + } + } + // END ADT MODIFICATIONS + + private Image m_bufferedImage; + private int m_rowHeight; + private int m_selection; + private int m_page; + private int m_splitter = -1; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public PropertyTable(Composite parent, int style) { + super(parent, style | SWT.V_SCROLL | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE); + hookControlEvents(); + // calculate sizes + { + GC gc = new GC(this); + try { + m_rowHeight = 1 + gc.getFontMetrics().getHeight() + 1; + } finally { + gc.dispose(); + } + } + // install tooltip helper + m_tooltipHelper = new PropertyTableTooltipHelper(this); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Events + // + //////////////////////////////////////////////////////////////////////////// + /** + * Adds listeners for events. + */ + private void hookControlEvents() { + addListener(SWT.Dispose, new Listener() { + @Override + public void handleEvent(Event event) { + disposeBufferedImage(); + } + }); + addListener(SWT.Resize, new Listener() { + @Override + public void handleEvent(Event event) { + handleResize(); + } + }); + addListener(SWT.Paint, new Listener() { + @Override + public void handleEvent(Event event) { + handlePaint(event.gc, event.x, event.y, event.width, event.height); + } + }); + getVerticalBar().addListener(SWT.Selection, new Listener() { + @Override + public void handleEvent(Event event) { + handleVerticalScrolling(); + } + }); + addMouseListener(new MouseAdapter() { + @Override + public void mouseDown(MouseEvent event) { + forceFocus(); + handleMouseDown(event); + } + + @Override + public void mouseUp(MouseEvent event) { + handleMouseUp(event); + } + + @Override + public void mouseDoubleClick(MouseEvent event) { + handleMouseDoubleClick(event); + } + }); + addMouseMoveListener(new MouseMoveListener() { + @Override + public void mouseMove(MouseEvent event) { + handleMouseMove(event); + } + }); + // keyboard + addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + handleKeyDown(e); + } + }); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Events: dispose, resize, scroll + // + //////////////////////////////////////////////////////////////////////////// + /** + * Disposes image used for double buffered painting. + */ + private void disposeBufferedImage() { + if (m_bufferedImage != null) { + m_bufferedImage.dispose(); + m_bufferedImage = null; + } + } + + /** + * Handles {@link SWT#Resize} event. + */ + private void handleResize() { + disposeBufferedImage(); + configureScrolling(); + // splitter + { + // set default value for splitter + if (m_splitter <= MIN_COLUMN_WIDTH) { + m_splitter = Math.max((int) (getClientArea().width * 0.4), MIN_COLUMN_WIDTH); + } + configureSplitter(); + } + } + + /** + * Handles {@link SWT#Selection} event for vertical {@link ScrollBar}. + */ + private void handleVerticalScrolling() { + ScrollBar verticalBar = getVerticalBar(); + if (verticalBar.getEnabled()) { + // update selection + m_selection = verticalBar.getSelection(); + // redraw (but not include vertical bar to avoid flashing) + { + Rectangle clientArea = getClientArea(); + redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false); + } + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Keyboard + // + //////////////////////////////////////////////////////////////////////////// + /** + * Handles {@link SWT#KeyDown} event. + */ + private void handleKeyDown(KeyEvent e) { + if (m_activePropertyInfo != null) { + try { + Property property = m_activePropertyInfo.getProperty(); + // expand/collapse + if (m_activePropertyInfo.isComplex()) { + if (!m_activePropertyInfo.isExpanded() + && (e.character == '+' || e.keyCode == SWT.ARROW_RIGHT)) { + m_activePropertyInfo.expand(); + configureScrolling(); + return; + } + if (m_activePropertyInfo.isExpanded() + && (e.character == '-' || e.keyCode == SWT.ARROW_LEFT)) { + m_activePropertyInfo.collapse(); + configureScrolling(); + return; + } + } + // navigation + if (navigate(e)) { + return; + } + // editor activation + if (e.character == ' ' || e.character == SWT.CR) { + activateEditor(property, null); + return; + } + // DEL + if (e.keyCode == SWT.DEL) { + e.doit = false; + property.setValue(Property.UNKNOWN_VALUE); + return; + } + // send to editor + property.getEditor().keyDown(this, property, e); + } catch (Throwable ex) { + DesignerPlugin.log(ex); + } + } + } + + /** + * @return true if given {@link KeyEvent} was navigation event, so new + * {@link PropertyInfo} was selected. + */ + public boolean navigate(KeyEvent e) { + int index = m_properties.indexOf(m_activePropertyInfo); + Rectangle clientArea = getClientArea(); + // + int newIndex = index; + if (e.keyCode == SWT.HOME) { + newIndex = 0; + } else if (e.keyCode == SWT.END) { + newIndex = m_properties.size() - 1; + } else if (e.keyCode == SWT.PAGE_UP) { + newIndex = Math.max(index - m_page + 1, 0); + } else if (e.keyCode == SWT.PAGE_DOWN) { + newIndex = Math.min(index + m_page - 1, m_properties.size() - 1); + } else if (e.keyCode == SWT.ARROW_UP) { + newIndex = Math.max(index - 1, 0); + } else if (e.keyCode == SWT.ARROW_DOWN) { + newIndex = Math.min(index + 1, m_properties.size() - 1); + } + // activate new property + if (newIndex != index && newIndex < m_properties.size()) { + setActivePropertyInfo(m_properties.get(newIndex)); + // check for scrolling + int y = m_rowHeight * (newIndex - m_selection); + if (y < 0) { + m_selection = newIndex; + configureScrolling(); + } else if (y + m_rowHeight > clientArea.height) { + m_selection = newIndex - m_page + 1; + configureScrolling(); + } + // repaint + redraw(); + return true; + } + // no navigation change + return false; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Events: mouse + // + //////////////////////////////////////////////////////////////////////////// + private boolean m_splitterResizing; + /** + * We do expand/collapse on to events: click on state sign and on double click. But when we double + * click on state sign, we will have two events, so we should ignore double click. + */ + private long m_lastExpandCollapseTime; + + /** + * Handles {@link SWT#MouseDown} event. + */ + private void handleMouseDown(MouseEvent event) { + m_splitterResizing = event.button == 1 && m_properties != null && isLocationSplitter(event.x); + // click in property + if (!m_splitterResizing && m_properties != null) { + int propertyIndex = getPropertyIndex(event.y); + if (propertyIndex >= m_properties.size()) { + return; + } + // prepare property + setActivePropertyInfo(m_properties.get(propertyIndex)); + Property property = m_activePropertyInfo.getProperty(); + // de-activate current editor + deactivateEditor(true); + redraw(); + // activate editor + if (isLocationValue(event.x)) { + activateEditor(property, getValueRelativeLocation(event.x, event.y)); + } + } + } + + /** + * Handles {@link SWT#MouseUp} event. + */ + private void handleMouseUp(MouseEvent event) { + if (event.button == 1) { + // resize splitter + if (m_splitterResizing) { + m_splitterResizing = false; + return; + } + // if out of bounds, then ignore + if (!getClientArea().contains(event.x, event.y)) { + return; + } + // update + if (m_properties != null) { + int index = getPropertyIndex(event.y); + if (index < m_properties.size()) { + PropertyInfo propertyInfo = m_properties.get(index); + // check for expand/collapse + if (isLocationState(propertyInfo, event.x)) { + try { + m_lastExpandCollapseTime = System.currentTimeMillis(); + propertyInfo.flip(); + configureScrolling(); + } catch (Throwable e) { + DesignerPlugin.log(e); + } + } + } + } + } + } + + /** + * Handles {@link SWT#MouseDoubleClick} event. + */ + private void handleMouseDoubleClick(MouseEvent event) { + if (System.currentTimeMillis() - m_lastExpandCollapseTime > getDisplay().getDoubleClickTime()) { + try { + if (m_activePropertyInfo != null) { + if (m_activePropertyInfo.isComplex()) { + m_activePropertyInfo.flip(); + configureScrolling(); + } else { + Property property = m_activePropertyInfo.getProperty(); + property.getEditor().doubleClick(property, getValueRelativeLocation(event.x, event.y)); + } + } + } catch (Throwable e) { + handleException(e); + } + } + } + + /** + * Handles {@link SWT#MouseMove} event. + */ + private void handleMouseMove(MouseEvent event) { + int x = event.x; + // resize splitter + if (m_splitterResizing) { + m_splitter = x; + configureSplitter(); + redraw(); + return; + } + // if out of bounds, then ignore + if (!getClientArea().contains(event.x, event.y)) { + return; + } + // update + if (m_properties != null) { + // update cursor + if (isLocationSplitter(x)) { + setCursor(ICursorConstants.SIZEWE); + } else { + setCursor(null); + } + // update tooltip helper + updateTooltip(event); + } else { + updateTooltipNoProperty(); + } + } + + /** + * Updates {@link PropertyTableTooltipHelper}. + */ + private void updateTooltip(MouseEvent event) { + int x = event.x; + int propertyIndex = getPropertyIndex(event.y); + // + if (propertyIndex < m_properties.size()) { + PropertyInfo propertyInfo = m_properties.get(propertyIndex); + Property property = propertyInfo.getProperty(); + int y = (propertyIndex - m_selection) * m_rowHeight; + // check for title + { + int titleX = getTitleTextX(propertyInfo); + int titleRight = m_splitter - 2; + if (titleX <= x && x < titleRight) { + m_tooltipHelper.update(property, true, false, titleX, titleRight, y, m_rowHeight); + return; + } + } + // check for value + { + int valueX = m_splitter + 3; + if (x > valueX) { + m_tooltipHelper.update( + property, + false, + true, + valueX, + getClientArea().width, + y, + m_rowHeight); + } + } + } else { + updateTooltipNoProperty(); + } + } + + private void updateTooltipNoProperty() { + m_tooltipHelper.update(null, false, false, 0, 0, 0, 0); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Editor + // + //////////////////////////////////////////////////////////////////////////// + private PropertyInfo m_activePropertyInfo; + private String m_activePropertyId; + private PropertyEditor m_activeEditor; + + /** + * Tries to activate editor for {@link PropertyInfo} under cursor. + * + * @param location + * the mouse location, if editor is activated using mouse click, or null if + * it is activated using keyboard. + */ + public void activateEditor(Property property, Point location) { + try { + // de-activate old editor + deactivateEditor(true); + // activate editor + PropertyEditor editor = property.getEditor(); + try { + if (editor.activate(this, property, location)) { + m_activeEditor = editor; + } + } catch (Throwable e) { + handleException(e); + } + // set bounds + setActiveEditorBounds(); + } catch (Throwable e) { + DesignerPlugin.log(e); + } + } + + /** + * Deactivates current {@link PropertyEditor}. + */ + public void deactivateEditor(boolean save) { + if (m_activeEditor != null) { + PropertyEditor activeEditor = m_activeEditor; + m_activeEditor = null; + if (m_activePropertyInfo != null && m_activePropertyInfo.m_property != null) { + activeEditor.deactivate(this, m_activePropertyInfo.m_property, save); + } + } + } + + /** + * Sets correct bounds for active editor, for example we need update bounds after scrolling. + */ + private void setActiveEditorBounds() { + if (m_activeEditor != null) { + int index = m_properties.indexOf(m_activePropertyInfo); + if (index == -1) { + // it is possible that active property was hidden because its parent was collapsed + deactivateEditor(true); + } else { + // prepare bounds for editor + Rectangle bounds; + { + Rectangle clientArea = getClientArea(); + int x = m_splitter + 1; + int width = clientArea.width - x - MARGIN_RIGHT; + int y = m_rowHeight * (index - m_selection) + 1; + int height = m_rowHeight - 1; + bounds = new Rectangle(x, y, width, height); + } + // update bounds using presentation + { + PropertyEditorPresentation presentation = m_activeEditor.getPresentation(); + if (presentation != null) { + int presentationWidth = + presentation.show( + this, + m_activePropertyInfo.m_property, + bounds.x, + bounds.y, + bounds.width, + bounds.height); + bounds.width -= presentationWidth; + } + } + // set editor bounds + m_activeEditor.setBounds(bounds); + } + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Exceptions + // + //////////////////////////////////////////////////////////////////////////// + private IPropertyExceptionHandler m_exceptionHandler; + + /** + * Sets {@link IPropertyExceptionHandler} for handling all exceptions. + */ + public void setExceptionHandler(IPropertyExceptionHandler exceptionHandler) { + m_exceptionHandler = exceptionHandler; + } + + /** + * Handles given {@link Throwable}.
+ * Right now it just logs it, but in future we can open some dialog here. + */ + public void handleException(Throwable e) { + m_exceptionHandler.handle(e); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Scrolling + // + //////////////////////////////////////////////////////////////////////////// + /** + * Configures vertical {@link ScrollBar}. + */ + private void configureScrolling() { + ScrollBar verticalBar = getVerticalBar(); + if (m_properties == null) { + verticalBar.setEnabled(false); + } else { + m_page = getClientArea().height / m_rowHeight; + m_selection = Math.max(0, Math.min(m_properties.size() - m_page, m_selection)); + verticalBar.setValues(m_selection, 0, m_properties.size(), m_page, 1, m_page); + // enable/disable scrolling + if (m_properties.size() <= m_page) { + verticalBar.setEnabled(false); + } else { + verticalBar.setEnabled(true); + } + } + // redraw, we reconfigure scrolling only if list of properties was changed, so we should redraw + redraw(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Location/size utils + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the X position for first pixel of {@link PropertyInfo} title (location of + * state image). + */ + private int getTitleX(PropertyInfo propertyInfo) { + return MARGIN_LEFT + getLevelIndent() * propertyInfo.getLevel(); + } + + /** + * @return the X position for first pixel of {@link PropertyInfo} title text. + */ + private int getTitleTextX(PropertyInfo propertyInfo) { + return getTitleX(propertyInfo) + getLevelIndent(); + } + + /** + * @return the indentation for single level. + */ + private int getLevelIndent() { + return m_stateWidth + STATE_IMAGE_MARGIN_RIGHT; + } + + /** + * Checks horizontal splitter value to boundary values. + */ + private void configureSplitter() { + Rectangle clientArea = getClientArea(); + // check title width + if (m_splitter < MIN_COLUMN_WIDTH) { + m_splitter = MIN_COLUMN_WIDTH; + } + // check value width + if (clientArea.width - m_splitter < MIN_COLUMN_WIDTH) { + m_splitter = clientArea.width - MIN_COLUMN_WIDTH; + } + } + + /** + * @return the index in {@link #m_properties} corresponding given y location. + */ + private int getPropertyIndex(int y) { + return m_selection + y / m_rowHeight; + } + + /** + * @return true if given x coordinate is on state (plus/minus) image. + */ + private boolean isLocationState(PropertyInfo propertyInfo, int x) { + int levelX = getTitleX(propertyInfo); + return propertyInfo.isComplex() && levelX <= x && x <= levelX + m_stateWidth; + } + + /** + * Returns true if x coordinate is on splitter. + */ + private boolean isLocationSplitter(int x) { + return Math.abs(m_splitter - x) < 2; + } + + /** + * @return true if given x is on value part of property. + */ + private boolean isLocationValue(int x) { + return x > m_splitter + 2; + } + + /** + * @param x + * the {@link PropertyTable} relative coordinate. + * @param y + * the {@link PropertyTable} relative coordinate. + * + * @return the location relative to the value part of property. + */ + private Point getValueRelativeLocation(int x, int y) { + return new Point(x - (m_splitter + 2), y - m_rowHeight * getPropertyIndex(y)); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + /** + * Shows or hides {@link Property}-s with {@link PropertyCategory#ADVANCED}. + */ + public void setShowAdvancedProperties(boolean showAdvancedProperties) { + m_showAdvancedProperties = showAdvancedProperties; + setInput0(); + } + + /** + * Sets the array of {@link Property}'s to display/edit. + */ + public void setInput(Property[] properties) { + m_rawProperties = properties; + setInput0(); + } + + private void setInput0() { + // send "hide" to all PropertyEditorPresentation's + if (m_properties != null) { + for (PropertyInfo propertyInfo : m_properties) { + Property property = propertyInfo.getProperty(); + // hide presentation + { + PropertyEditorPresentation presentation = property.getEditor().getPresentation(); + if (presentation != null) { + presentation.hide(this, property); + } + } + } + } + // set new properties + if (m_rawProperties == null || m_rawProperties.length == 0) { + deactivateEditor(false); + m_properties = Lists.newArrayList(); + } else { + try { + // add PropertyInfo for each Property + m_properties = Lists.newArrayList(); + for (Property property : m_rawProperties) { + if (rawProperties_shouldShow(property)) { + PropertyInfo propertyInfo = new PropertyInfo(property); + m_properties.add(propertyInfo); + } + } + // expand properties using history + while (true) { + boolean expanded = false; + List currentProperties = Lists.newArrayList(m_properties); + for (PropertyInfo propertyInfo : currentProperties) { + expanded |= propertyInfo.expandFromHistory(); + } + // stop + if (!expanded) { + break; + } + } + } catch (Throwable e) { + DesignerPlugin.log(e); + } + } + // update active property + if (m_activePropertyId != null) { + PropertyInfo newActivePropertyInfo = null; + // try to find corresponding PropertyInfo + if (m_properties != null) { + for (PropertyInfo propertyInfo : m_properties) { + if (propertyInfo.m_id.equals(m_activePropertyId)) { + newActivePropertyInfo = propertyInfo; + break; + } + } + } + // set new PropertyInfo + setActivePropertyInfo(newActivePropertyInfo); + } + // update scroll bar + configureScrolling(); + } + + /** + * @return true if given {@link Property} should be displayed. + */ + private boolean rawProperties_shouldShow(Property property) throws Exception { + PropertyCategory category = getCategory(property); + // filter out hidden properties + if (category.isHidden()) { + return false; + } + // filter out advanced properties + if (category.isAdvanced()) { + if (!m_showAdvancedProperties && !property.isModified()) { + return false; + } + } + if (category.isAdvancedReally()) { + return m_showAdvancedProperties; + } + // OK + return true; + } + + /** + * Activates given {@link Property}. + */ + public void setActiveProperty(Property property) { + for (PropertyInfo propertyInfo : m_properties) { + if (propertyInfo.m_property == property) { + setActivePropertyInfo(propertyInfo); + break; + } + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access: only for testing + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the count of properties in "expanded" list. + */ + public int forTests_getPropertiesCount() { + return m_properties.size(); + } + + /** + * @return the {@link Property} from "expanded" list. + */ + public Property forTests_getProperty(int index) { + return m_properties.get(index).getProperty(); + } + + /** + * Expands the {@link PropertyInfo} with given index. + */ + public void forTests_expand(int index) throws Exception { + m_properties.get(index).expand(); + } + + /** + * @return the position of splitter. + */ + public int forTests_getSplitter() { + return m_splitter; + } + + /** + * @return the location of state image (plus/minus) for given {@link Property}. + */ + public Point forTests_getStateLocation(Property property) { + PropertyInfo propertyInfo = getPropertyInfo(property); + if (propertyInfo != null) { + int index = m_properties.indexOf(propertyInfo); + int x = getTitleX(propertyInfo); + int y = m_rowHeight * (index - m_selection) + 1; + return new Point(x, y); + } + return null; + } + + /** + * @return the location of state image (plus/minus) for given {@link Property}. + */ + public Point forTests_getValueLocation(Property property) { + PropertyInfo propertyInfo = getPropertyInfo(property); + if (propertyInfo != null) { + int index = m_properties.indexOf(propertyInfo); + int x = m_splitter + 5; + int y = m_rowHeight * (index - m_selection) + 1; + return new Point(x, y); + } + return null; + } + + /** + * @return the active {@link PropertyEditor}. + */ + public PropertyEditor forTests_getActiveEditor() { + return m_activeEditor; + } + + /** + * @return the {@link PropertyCategory} that is used by this {@link PropertyTable} to display. + */ + public PropertyCategory forTests_getCategory(Property property) { + return getCategory(property); + } + + /** + * @return the {@link PropertyInfo}for given {@link Property}. + */ + private PropertyInfo getPropertyInfo(Property property) { + for (PropertyInfo propertyInfo : m_properties) { + if (propertyInfo.getProperty() == property) { + return propertyInfo; + } + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // ISelectionProvider + // + //////////////////////////////////////////////////////////////////////////// + private final List m_selectionListeners = Lists.newArrayList(); + + @Override +public void addSelectionChangedListener(ISelectionChangedListener listener) { + if (!m_selectionListeners.contains(listener)) { + m_selectionListeners.add(listener); + } + } + + @Override +public void removeSelectionChangedListener(ISelectionChangedListener listener) { + m_selectionListeners.add(listener); + } + + @Override +public ISelection getSelection() { + if (m_activePropertyInfo != null) { + return new StructuredSelection(m_activePropertyInfo.getProperty()); + } else { + return StructuredSelection.EMPTY; + } + } + + @Override + public void setSelection(ISelection selection) { + throw new UnsupportedOperationException(); + } + + /** + * Sets the new active {@link PropertyInfo} and sends event to {@link ISelectionChangedListener} + * 's. + */ + private void setActivePropertyInfo(PropertyInfo activePropertyInfo) { + m_activePropertyInfo = activePropertyInfo; + // update m_activePropertyId only when really select property, + // not just remove selection because there are no corresponding property for old active + // so, later for some other component, if we don't select other property, old active will be selected + if (m_activePropertyInfo != null) { + m_activePropertyId = m_activePropertyInfo.m_id; + } + // make sure that active property is visible + if (m_activePropertyInfo != null) { + int row = m_properties.indexOf(m_activePropertyInfo); + if (m_selection <= row && row < m_selection + m_page) { + } else { + m_selection = row; + configureScrolling(); + } + } + // send events + SelectionChangedEvent selectionEvent = new SelectionChangedEvent(this, getSelection()); + for (ISelectionChangedListener listener : m_selectionListeners) { + listener.selectionChanged(selectionEvent); + } + // re-draw + redraw(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Painting + // + //////////////////////////////////////////////////////////////////////////// + private boolean m_painting; + private Font m_baseFont; + private Font m_boldFont; + private Font m_italicFont; + + /** + * Handles {@link SWT#Paint} event. + */ + private void handlePaint(GC gc, int x, int y, int width, int height) { + // sometimes we disable Eclipse Shell to prevent user actions, but we do this for short time + if (!isEnabled()) { + return; + } + // prevent recursion + if (m_painting) { + return; + } + m_painting = true; + // + try { + setActiveEditorBounds(); + // prepare buffered image + if (m_bufferedImage == null || m_bufferedImage.isDisposed()) { + Point size = getSize(); + m_bufferedImage = new Image(DesignerPlugin.getStandardDisplay(), size.x, size.y); + } + // prepare buffered GC + GC bufferedGC = null; + try { + // perform some drawing + { + bufferedGC = new GC(m_bufferedImage); + bufferedGC.setClipping(x, y, width, height); + bufferedGC.setBackground(gc.getBackground()); + bufferedGC.setForeground(gc.getForeground()); + bufferedGC.setFont(gc.getFont()); + bufferedGC.setLineStyle(gc.getLineStyle()); + bufferedGC.setLineWidth(gc.getLineWidth()); + } + // fill client area + { + Rectangle clientArea = getClientArea(); + bufferedGC.setBackground(COLOR_BACKGROUND); + bufferedGC.fillRectangle(clientArea); + } + // draw content + if (m_properties == null || m_properties.size() == 0) { + drawEmptyContent(bufferedGC); + } else { + drawContent(bufferedGC); + } + } finally { + // flush image + if (bufferedGC != null) { + bufferedGC.dispose(); + } + } + gc.drawImage(m_bufferedImage, 0, 0); + } finally { + m_painting = false; + } + } + + /** + * Draws content when there are no properties. + */ + private void drawEmptyContent(GC gc) { + Rectangle area = getClientArea(); + // draw message + gc.setForeground(COLOR_NO_PROPERTIES); + DrawUtils.drawStringCHCV( + gc, + "", + 0, + 0, + area.width, + area.height); + } + + /** + * Draws all {@link PropertyInfo}'s, separators, etc. + */ + private void drawContent(GC gc) { + Rectangle clientArea = getClientArea(); + // prepare fonts + m_baseFont = gc.getFont(); + m_boldFont = DrawUtils.getBoldFont(m_baseFont); + m_italicFont = DrawUtils.getItalicFont(m_baseFont); + // show presentations + int[] presentationsWidth = showPresentations(clientArea); + // draw properties + { + int y = clientArea.y - m_rowHeight * m_selection; + for (int i = 0; i < m_properties.size(); i++) { + // skip, if not visible yet + if (y + m_rowHeight < 0) { + y += m_rowHeight; + continue; + } + // stop, if already invisible + if (y > clientArea.height) { + break; + } + // draw single property + { + PropertyInfo propertyInfo = m_properties.get(i); + drawProperty(gc, propertyInfo, y + 1, m_rowHeight - 1, clientArea.width + - presentationsWidth[i]); + y += m_rowHeight; + } + // draw row separator + gc.setForeground(COLOR_LINE); + gc.drawLine(0, y, clientArea.width, y); + } + } + // draw expand line + drawExpandLines(gc, clientArea); + // draw rectangle around table + gc.setForeground(COLOR_LINE); + gc.drawRectangle(0, 0, clientArea.width - 1, clientArea.height - 1); + // draw splitter + gc.setForeground(COLOR_LINE); + gc.drawLine(m_splitter, 0, m_splitter, clientArea.height); + // dispose font + m_boldFont.dispose(); + m_italicFont.dispose(); + } + + /** + * Shows {@link PropertyEditorPresentation}'s for all {@link Property}'s, i.e. updates also their + * bounds. So, some {@link PropertyEditorPresentation}'s become invisible because they are moved + * above or below visible client area. + * + * @return the array of width for each {@link PropertyEditorPresentation}'s, consumed on the + * right. + */ + private int[] showPresentations(Rectangle clientArea) { + int[] presentationsWidth = new int[m_properties.size()]; + // prepare value rectangle + int x = m_splitter + 4; + int w = clientArea.width - x - MARGIN_RIGHT; + // show presentation's for all properties + int y = clientArea.y - m_rowHeight * m_selection; + for (int i = 0; i < m_properties.size(); i++) { + PropertyInfo propertyInfo = m_properties.get(i); + Property property = propertyInfo.getProperty(); + PropertyEditorPresentation presentation = property.getEditor().getPresentation(); + if (presentation != null) { + presentationsWidth[i] = presentation.show(this, property, x, y + 1, w, m_rowHeight - 1); + } + y += m_rowHeight; + } + return presentationsWidth; + } + + /** + * Draws lines from expanded complex property to its last sub-property. + */ + private void drawExpandLines(GC gc, Rectangle clientArea) { + int height = m_rowHeight - 1; + int xOffset = m_plusImage.getBounds().width / 2; + int yOffset = (height - m_plusImage.getBounds().width) / 2; + // + int y = clientArea.y - m_selection * m_rowHeight; + gc.setForeground(COLOR_COMPLEX_LINE); + for (int i = 0; i < m_properties.size(); i++) { + PropertyInfo propertyInfo = m_properties.get(i); + // + if (propertyInfo.isExpanded()) { + int index = m_properties.indexOf(propertyInfo); + // prepare index of last sub-property + int index2 = index; + for (; index2 < m_properties.size(); index2++) { + PropertyInfo nextPropertyInfo = m_properties.get(index2); + if (nextPropertyInfo != propertyInfo + && nextPropertyInfo.getLevel() <= propertyInfo.getLevel()) { + break; + } + } + index2--; + // draw line if there are children + if (index2 > index) { + int x = getTitleX(propertyInfo) + xOffset; + int y1 = y + height - yOffset; + int y2 = y + m_rowHeight * (index2 - index) + m_rowHeight / 2; + gc.drawLine(x, y1, x, y2); + gc.drawLine(x, y2, x + m_rowHeight / 3, y2); + } + } + // + y += m_rowHeight; + } + } + + /** + * Draws single {@link PropertyInfo} in specified rectangle. + */ + private void drawProperty(GC gc, PropertyInfo propertyInfo, int y, int height, int width) { + // remember colors + Color oldBackground = gc.getBackground(); + Color oldForeground = gc.getForeground(); + // draw property + try { + Property property = propertyInfo.getProperty(); + boolean isActiveProperty = + m_activePropertyInfo != null && m_activePropertyInfo.getProperty() == property; + // set background + boolean modified = property.isModified(); + { + if (isActiveProperty) { + gc.setBackground(COLOR_PROPERTY_BG_SELECTED); + } else { + if (modified) { + gc.setBackground(COLOR_PROPERTY_BG_MODIFIED); + } else { + gc.setBackground(COLOR_PROPERTY_BG); + } + } + gc.fillRectangle(0, y, width, height); + } + // draw state image + if (propertyInfo.isShowComplex()) { + Image stateImage = propertyInfo.isExpanded() ? m_minusImage : m_plusImage; + DrawUtils.drawImageCV(gc, stateImage, getTitleX(propertyInfo), y, height); + } + // draw title + { + // configure GC + { + gc.setForeground(COLOR_PROPERTY_FG_TITLE); + // check category + if (getCategory(property).isAdvanced()) { + gc.setForeground(COLOR_PROPERTY_FG_ADVANCED); + gc.setFont(m_italicFont); + } else if (getCategory(property).isPreferred() || getCategory(property).isSystem()) { + gc.setFont(m_boldFont); + } + // check for active + if (isActiveProperty) { + gc.setForeground(COLOR_PROPERTY_FG_SELECTED); + } + } + // paint title + int x = getTitleTextX(propertyInfo); + DrawUtils.drawStringCV(gc, property.getTitle(), x, y, m_splitter - x, height); + } + // draw value + { + // configure GC + gc.setFont(m_baseFont); + if (!isActiveProperty) { + gc.setForeground(COLOR_PROPERTY_FG_VALUE); + } + // prepare value rectangle + int x = m_splitter + 4; + int w = width - x - MARGIN_RIGHT; + // paint value + + // BEGIN ADT MODIFICATIONS + if (!modified) { + gc.setForeground(COLOR_PROPERTY_FG_DEFAULT); + } + // END ADT MODIFICATIONS + + property.getEditor().paint(property, gc, x, y, w, height); + } + } catch (Throwable e) { + DesignerPlugin.log(e); + } finally { + // restore colors + gc.setBackground(oldBackground); + gc.setForeground(oldForeground); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // PropertyCategory + // + //////////////////////////////////////////////////////////////////////////// + private PropertyCategoryProvider m_propertyCategoryProvider = + PropertyCategoryProviders.fromProperty(); + + /** + * Sets the {@link PropertyCategoryProvider} that can be used to tweak usual + * {@link PropertyCategory}. + */ + public void setPropertyCategoryProvider(PropertyCategoryProvider propertyCategoryProvider) { + m_propertyCategoryProvider = propertyCategoryProvider; + } + + /** + * @return the {@link PropertyCategory} that is used by this {@link PropertyTable} to display. + */ + private PropertyCategory getCategory(Property property) { + return m_propertyCategoryProvider.getCategory(property); + } + + //////////////////////////////////////////////////////////////////////////// + // + // PropertyInfo + // + //////////////////////////////////////////////////////////////////////////// + /** + * Class with information about single {@link Property}. + * + * @author scheglov_ke + */ + private final class PropertyInfo { + private final String m_id; + private final int m_level; + private final Property m_property; + private final boolean m_stateComplex; + private boolean m_stateExpanded; + private List m_children; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public PropertyInfo(Property property) { + this(property, "", 0); + } + + private PropertyInfo(Property property, String idPrefix, int level) { + // BEGIN ADT MODIFICATIONS + //m_id = idPrefix + "|" + property.getTitle(); + m_id = idPrefix + "|" + property.getName(); + // END ADT MODIFICATIONS + m_level = level; + m_property = property; + m_stateComplex = property.getEditor() instanceof IComplexPropertyEditor; + } + + //////////////////////////////////////////////////////////////////////////// + // + // State + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return true if this property is complex. + */ + public boolean isComplex() { + return m_stateComplex; + } + + public boolean isShowComplex() throws Exception { + if (m_stateComplex) { + prepareChildren(); + return m_children != null && !m_children.isEmpty(); + } + return false; + } + + /** + * @return true if this complex property is expanded. + */ + public boolean isExpanded() { + return m_stateExpanded; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the level of this property, i.e. on which level of complex property it is located. + */ + public int getLevel() { + return m_level; + } + + /** + * @return the {@link Property}. + */ + public Property getProperty() { + return m_property; + } + + /** + * Flips collapsed/expanded state and adds/removes sub-properties. + */ + public void flip() throws Exception { + Assert.isTrue(m_stateComplex); + if (m_stateExpanded) { + collapse(); + } else { + expand(); + } + } + + /** + * Expands this property. + */ + public void expand() throws Exception { + Assert.isTrue(m_stateComplex); + Assert.isTrue(!m_stateExpanded); + // + m_stateExpanded = true; + m_expandedIds.add(m_id); + // BEGIN ADT MODIFICATIONS + if (m_collapsedIds != null) { + m_collapsedIds.remove(m_id); + } + // END ADT MODIFICATIONS + prepareChildren(); + // + int index = m_properties.indexOf(this); + addChildren(index + 1); + } + + /** + * Collapses this property. + */ + public void collapse() throws Exception { + Assert.isTrue(m_stateComplex); + Assert.isTrue(m_stateExpanded); + // + m_stateExpanded = false; + m_expandedIds.remove(m_id); + // BEGIN ADT MODIFICATIONS + if (m_collapsedIds != null) { + m_collapsedIds.add(m_id); + } + // END ADT MODIFICATIONS + prepareChildren(); + // + int index = m_properties.indexOf(this); + removeChildren(index + 1); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Internal + // + //////////////////////////////////////////////////////////////////////////// + /** + * Adds children properties. + * + * @return the index for new properties to add. + */ + private int addChildren(int index) throws Exception { + prepareChildren(); + for (PropertyInfo child : m_children) { + // skip if should not display raw Property + if (!rawProperties_shouldShow(child.m_property)) { + continue; + } + // add child + m_properties.add(index++, child); + // add children of current child + if (child.isExpanded()) { + index = child.addChildren(index); + } + } + return index; + } + + /** + * Removes children properties. + */ + private void removeChildren(int index) throws Exception { + prepareChildren(); + for (PropertyInfo child : m_children) { + // skip if should not display raw Property + if (!rawProperties_shouldShow(child.m_property)) { + continue; + } + // hide presentation + { + PropertyEditorPresentation presentation = + child.getProperty().getEditor().getPresentation(); + if (presentation != null) { + presentation.hide(PropertyTable.this, child.getProperty()); + } + } + // remove child + m_properties.remove(index); + // remove children of current child + if (child.isExpanded()) { + child.removeChildren(index); + } + } + } + + /** + * Prepares children {@link PropertyInfo}'s, for sub-properties. + */ + private void prepareChildren() throws Exception { + if (m_children == null) { + m_children = Lists.newArrayList(); + for (Property subProperty : getSubProperties()) { + PropertyInfo subPropertyInfo = createSubPropertyInfo(subProperty); + m_children.add(subPropertyInfo); + } + } + } + + private PropertyInfo createSubPropertyInfo(Property subProperty) { + return new PropertyInfo(subProperty, m_id, m_level + 1); + } + + private Property[] getSubProperties() throws Exception { + IComplexPropertyEditor complexEditor = (IComplexPropertyEditor) m_property.getEditor(); + List subProperties = Lists.newArrayList(); + for (Property subProperty : complexEditor.getProperties(m_property)) { + if (getCategory(subProperty).isHidden() && !subProperty.isModified()) { + // skip hidden properties + continue; + } + subProperties.add(subProperty); + } + return subProperties.toArray(new Property[subProperties.size()]); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Persistent expanding support + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return true if this {@link PropertyInfo} was expanded from history. + */ + public boolean expandFromHistory() throws Exception { + if (isComplex() && !isExpanded() && m_expandedIds.contains(m_id)) { + expand(); + return true; + } + // BEGIN ADT MODIFICATIONS + if (m_collapsedIds != null && isComplex() && !isExpanded() + && !m_collapsedIds.contains(m_id)) { + expand(); + return true; + } + // END ADT MODIFICATIONS + return false; + } + } + + // BEGIN ADT MODIFICATIONS + /** Collapse all top-level properties */ + public void collapseAll() { + try { + m_lastExpandCollapseTime = System.currentTimeMillis(); + if (m_collapsedIds != null) { + m_collapsedIds.addAll(m_expandedIds); + } + m_expandedIds.clear(); + setInput(m_rawProperties); + redraw(); + } catch (Throwable e) { + DesignerPlugin.log(e); + } + } + + /** Expand all top-level properties */ + public void expandAll() { + try { + m_lastExpandCollapseTime = System.currentTimeMillis(); + if (m_collapsedIds != null) { + m_collapsedIds.clear(); + } + m_expandedIds.clear(); + for (PropertyInfo info : m_properties) { + if (info.m_stateComplex) { + m_expandedIds.add(info.m_id); + } + } + setInput(m_rawProperties); + redraw(); + } catch (Throwable e) { + DesignerPlugin.log(e); + } + } + // END ADT MODIFICATIONS +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTableTooltipHelper.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTableTooltipHelper.java new file mode 100644 index 0000000..16b9d8f --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTableTooltipHelper.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.table; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.wb.internal.core.EnvironmentUtils; +import org.eclipse.wb.internal.core.model.property.Property; +import org.eclipse.wb.internal.core.utils.ui.GridLayoutFactory; + +/** + * Helper class for displaying tooltips. + * + * @author scheglov_ke + * @coverage core.model.property.table + */ +class PropertyTableTooltipHelper implements IPropertyTooltipSite { + private final PropertyTable m_table; + private Shell m_tooltip; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public PropertyTableTooltipHelper(PropertyTable table) { + m_table = table; + m_table.addListener(SWT.MouseHover, new Listener() { + @Override + public void handleEvent(Event event) { + if (event.stateMask == 0) { + showTooltip(); + } + } + }); + m_table.addListener(SWT.MouseExit, new Listener() { + @Override + public void handleEvent(Event event) { + // check, may be cursor is now on tooltip, so ignore this MouseExit + { + Control control = Display.getCurrent().getCursorControl(); + while (control != null) { + if (control == m_tooltip) { + return; + } + control = control.getParent(); + } + } + // no, we should hide tooltip + hideTooltip(); + } + }); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + private Property m_property; + private boolean m_onTitle; + private boolean m_onValue; + private int m_beginX; + private int m_endX; + private int m_y; + private int m_rowHeight; + + /** + * {@link PropertyTable} call this method to inform that cursor location was changed. + */ + public void update(Property property, + boolean onTitle, + boolean onValue, + int beginX, + int endX, + int y, + int rowHeight) { + m_property = property; + m_onTitle = onTitle; + m_onValue = onValue; + m_beginX = beginX; + m_endX = endX; + m_y = y; + m_rowHeight = rowHeight; + } + + //////////////////////////////////////////////////////////////////////////// + // + // IPropertyTooltipSite + // + //////////////////////////////////////////////////////////////////////////// + @Override +public PropertyTable getTable() { + return m_table; + } + + @Override +public void hideTooltip() { + if (m_tooltip != null && !m_tooltip.isDisposed()) { + m_tooltip.dispose(); + } + m_tooltip = null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Showing tooltip + // + //////////////////////////////////////////////////////////////////////////// + private void showTooltip() { + hideTooltip(); + // check for property + if (m_property == null) { + return; + } + // + if (m_onTitle) { + showTooltip(m_property.getAdapter(PropertyTooltipProvider.class), m_beginX, m_endX); + } + if (m_onValue) { + showTooltip(m_property.getEditor().getAdapter(PropertyTooltipProvider.class), + m_beginX, m_endX); + } + } + + private void showTooltip(PropertyTooltipProvider provider, int startX, int endX) { + if (provider == null) { + return; + } + // create Shell + { + m_tooltip = new Shell(m_table.getShell(), SWT.NO_FOCUS | SWT.ON_TOP | SWT.TOOL | SWT.SINGLE); + configureColors(m_tooltip); + GridLayoutFactory.create(m_tooltip).noMargins(); + } + // prepare control + Control control = provider.createTooltipControl(m_property, m_tooltip, endX - startX, this); + if (control == null) { + hideTooltip(); + return; + } + // show Shell + { + // prepare tooltip location + Point tooltipLocation; + if (provider.getTooltipPosition() == PropertyTooltipProvider.ON) { + tooltipLocation = m_table.toDisplay(new Point(startX, m_y)); + } else { + tooltipLocation = m_table.toDisplay(new Point(startX, m_y + m_rowHeight)); + } + // set location/size and open + m_tooltip.setLocation(tooltipLocation.x, tooltipLocation.y); + // for non-windows systems the tooltip may have invalid tooltip bounds + // because some widget's API functions may fail if tooltip content is not visible + // ex., on MacOSX tree widget's items has zero bounds since they are not yet visible. + // the workaround is to preset tooltip size to big values before any computeSize called. + if (!EnvironmentUtils.IS_WINDOWS) { + m_tooltip.setSize(1000, 1000); + } + m_tooltip.setSize(m_tooltip.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + provider.show(m_tooltip); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Utils + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sets given {@link Control} correct background/foreground for tooltips. + */ + private void configureColors(Control control) { + Display display = Display.getCurrent(); + control.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); + control.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipProvider.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipProvider.java new file mode 100644 index 0000000..1c3d4fe --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipProvider.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.table; + +import org.eclipse.wb.internal.core.model.property.Property; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; + +/** + * Provider for tooltip controls. + * + * @author scheglov_ke + * @coverage core.model.property.table + */ +public abstract class PropertyTooltipProvider { + /** + * Show tooltip directly on property row. + */ + public static final int ON = 0; + /** + * Show tooltip below property row. + */ + public static final int BELOW = 1; + + //////////////////////////////////////////////////////////////////////////// + // + // PropertyTooltipProvider + // + //////////////////////////////////////////////////////////////////////////// + /** + * Create tooltip control. + */ + public abstract Control createTooltipControl(Property property, + Composite parent, + int availableWidth, + IPropertyTooltipSite site); + + /** + * Shows tooltip {@link Shell}. + */ + public void show(Shell shell) { + shell.setVisible(true); + } + + /** + * Returns position for tooltip control. Usually we should show directly on same row, because we + * use tooltip to show just longer (full) text of property. But for "class" property we show + * hierarchy, so it is better show it below and allow user see also property row. + */ + public int getTooltipPosition() { + return ON; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Tooltip listener + // + //////////////////////////////////////////////////////////////////////////// + /** + * {@link Listener} that hides tooltip on mouse exit or click. + */ + protected static final class HideListener implements Listener { + private final IPropertyTooltipSite m_site; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public HideListener(IPropertyTooltipSite site) { + m_site = site; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Listener + // + //////////////////////////////////////////////////////////////////////////// + public void handleEvent(Event event) { + Control tooltipControl = (Control) event.widget; + switch (event.type) { + case SWT.MouseDown : { + PropertyTable table = m_site.getTable(); + // convert location from tooltip to table + Point p = new Point(event.x, event.y); + p = tooltipControl.toDisplay(p); + p = table.toControl(p); + // send MouseDown to table + Event newEvent = new Event(); + newEvent.x = p.x; + newEvent.y = p.y; + table.notifyListeners(SWT.MouseDown, newEvent); + // hide tooltip + m_site.hideTooltip(); + break; + } + case SWT.MouseExit : + m_site.hideTooltip(); + break; + } + } + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipTextProvider.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipTextProvider.java new file mode 100644 index 0000000..b2e9b69 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipTextProvider.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.model.property.table; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.wb.internal.core.model.property.Property; + +/** + * Implementation of {@link PropertyTooltipProvider} for text. + * + * @author scheglov_ke + * @coverage core.model.property.table + */ +public abstract class PropertyTooltipTextProvider extends PropertyTooltipProvider { + //////////////////////////////////////////////////////////////////////////// + // + // PropertyTooltipProvider + // + //////////////////////////////////////////////////////////////////////////// + @Override + public Control createTooltipControl(Property property, + Composite parent, + int availableWidth, + IPropertyTooltipSite site) { + // prepare header and content + String header = null; + String content = null; + try { + // BEGIN ADT MODIFICATIONS + // was: header = property.getTitle(); + header = property.getName(); + // END ADT MODIFICATIONS + content = getText(property); + } catch (Throwable e) { + } + if (header == null || content == null) { + return null; + } + // create tooltip Control + return HtmlTooltipHelper.createTooltipControl(parent, header, content, 8); + } + + @Override + public void show(Shell shell) { + // do nothing, Shell will be displayed when Browser will complete rendering + } + + //////////////////////////////////////////////////////////////////////////// + // + // Text + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the text to show as tooltip. + */ + protected abstract String getText(Property property) throws Exception; +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/Tooltip.css b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/Tooltip.css new file mode 100644 index 0000000..2ba2a02 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/Tooltip.css @@ -0,0 +1,35 @@ +/* Font definitions */ +html { font-family: 'Segoe UI','Verdana','Helvetica',sans-serif; font-size: 9pt; font-style: normal; font-weight: normal; } +body, h1, h2, h3, h4, h5, h6, p, table, td, caption, th, ul, ol, dl, li, dd, dt { font-size: 1em; } +pre { font-family: monospace; } + +/* Margins */ +body { overflow: auto; margin-top: 0px; margin-bottom: 0px; margin-left: 0.3em; margin-right: 0.3em; } +div { margin: 0px; } +h1 { margin-top: 0.3em; margin-bottom: 0.04em; } +h2 { margin-top: 2em; margin-bottom: 0.25em; } +h3 { margin-top: 1.7em; margin-bottom: 0.25em; } +h4 { margin-top: 2em; margin-bottom: 0.3em; } +h5 { margin-top: 0px; margin-bottom: 0px; } +p { margin-top: 0em; margin-bottom: 0em; } +pre { margin-left: 0.6em; } +ul { margin-top: 0px; margin-bottom: 1em; } +li { margin-top: 0px; margin-bottom: 0px; } +li p { margin-top: 0px; margin-bottom: 0px; } +ol { margin-top: 0px; margin-bottom: 1em; } +dl { margin-top: 0px; margin-bottom: 1em; } +dt { margin-top: 0px; margin-bottom: 0px; font-weight: bold; } +dd { margin-top: 0px; margin-bottom: 0px; } + +/* Styles and colors */ +a:link { color: #0000FF; } +a:hover { color: #000080; } +a:visited { text-decoration: underline; } +a.header:link { text-decoration: none; color: InfoText } +a.header:visited { text-decoration: none; color: InfoText } +a.header:hover { text-decoration: underline; color: #000080; } +h4 { font-style: italic; } +strong { font-weight: bold; } +em { font-style: italic; } +var { font-style: italic; } +th { font-weight: bold; } diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/Pair.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/Pair.java new file mode 100644 index 0000000..3df0e9b --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/Pair.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils; + +import com.google.common.base.Objects; + +/** + * Pair of two objects. + * + * @author scheglov_ke + * @coverage core.util + */ +public final class Pair { + private final L left; + private final R right; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public Pair(L left, R right) { + this.left = left; + this.right = right; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Object + // + //////////////////////////////////////////////////////////////////////////// + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Pair)) { + return false; + } + Pair other = (Pair) o; + return Objects.equal(getLeft(), other.getLeft()) + && Objects.equal(getRight(), other.getRight()); + } + + @Override + public int hashCode() { + int hLeft = getLeft() == null ? 0 : getLeft().hashCode(); + int hRight = getRight() == null ? 0 : getRight().hashCode(); + return hLeft + 37 * hRight; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + public L getLeft() { + return left; + } + + public R getRight() { + return right; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Factory + // + //////////////////////////////////////////////////////////////////////////// + public static Pair create(L left, R right) { + return new Pair(left, right); + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/AbstractControlActionsManager.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/AbstractControlActionsManager.java new file mode 100644 index 0000000..155b55c --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/AbstractControlActionsManager.java @@ -0,0 +1,174 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.binding.editors.controls; + +import com.google.common.collect.Lists; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.expressions.EvaluationResult; +import org.eclipse.core.expressions.Expression; +import org.eclipse.core.expressions.ExpressionInfo; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.ISources; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.IHandlerActivation; +import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds; + +import java.util.List; + +/** + * Manager for installing/unistalling global handlers for {@link Control} actions commands. + * + * @author sablin_aa + * @author mitin_aa + */ +public abstract class AbstractControlActionsManager { + @SuppressWarnings("deprecation") + protected final Object[] COMMAND_HANDLER_IDS = new Object[]{ + IWorkbenchActionDefinitionIds.COPY, + IWorkbenchActionDefinitionIds.CUT, + IWorkbenchActionDefinitionIds.PASTE, + IWorkbenchActionDefinitionIds.DELETE, + IWorkbenchActionDefinitionIds.SELECT_ALL, + IWorkbenchActionDefinitionIds.UNDO, + IWorkbenchActionDefinitionIds.REDO}; + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + protected final Control m_control; + private boolean m_active = false; + + public AbstractControlActionsManager(final Control control) { + m_control = control; + m_control.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + activateHandlers(); + m_active = true; + } + + @Override + public void focusLost(FocusEvent e) { + deactivateHandlers(); + m_active = false; + } + }); + m_control.addDisposeListener(new DisposeListener() { + @Override + public void widgetDisposed(DisposeEvent e) { + if (m_active) { + // deactivate on dispose + deactivateHandlers(); + } + } + }); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Handlers + // + //////////////////////////////////////////////////////////////////////////// + protected static final IHandler EMPTY_HANDLER = new AbstractHandler() { + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + // do nothing + return null; + } + + @Override + public boolean isEnabled() { + // of course, it is enabled ;) + return true; + } + + @Override + public boolean isHandled() { + // we do not handle the actions; since action not handled, its' underlying SWT event + // would not be filtered by workbench, so it get a chance to be handled by SWT code + // or to be passed to the OS; see WorkbenchKeyboard.press() method. + return false; + } + }; + + /** + * @returns the global handler service. + */ + public static IHandlerService getHandlerService() { + return (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + } + + /** + * Activates the handlers for list of commands (see COMMAND_HANDLERS) with:
+ * 1. The empty handler (except 'selectAll'), so underlying SWT event wouldn't be filtered by the + * workbench;
+ * 2. Highest priority {@link Expression}, so this handler has a chance to be set. + */ + protected void activateHandlers() { + IHandlerService service = getHandlerService(); + for (int i = 0; i < COMMAND_HANDLER_IDS.length; ++i) { + String actionName = (String) COMMAND_HANDLER_IDS[i]; + IHandler handler = getHandlerFor(actionName); + activateHandler(actionName, service, handler, new Expression() { + @Override + public EvaluationResult evaluate(IEvaluationContext context) throws CoreException { + return EvaluationResult.TRUE; + } + + @Override + public void collectExpressionInfo(ExpressionInfo info) { + // get the highest priority + // note, if someone else has such priority, there will be key-binding conflicts logged. + info.markSystemPropertyAccessed(); + info.markDefaultVariableAccessed(); + info.addVariableNameAccess(ISources.ACTIVE_MENU_NAME); + } + }); + } + } + + protected IHandler getHandlerFor(String actionName) { + return EMPTY_HANDLER; + } + + /** + * Activates handler and stores it into a collection for further deactivation. + */ + private final List m_activations = Lists.newLinkedList(); + + private void activateHandler(String actionName, + IHandlerService service, + IHandler handler, + Expression highPriorityExpression) { + // activate handler and store it into a collection for further deactivation + m_activations.add(service.activateHandler(actionName, handler, highPriorityExpression)); + } + + /** + * Deactivates all handlers and clears handlers collection. + */ + protected void deactivateHandlers() { + getHandlerService().deactivateHandlers(m_activations); + m_activations.clear(); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/DefaultControlActionsManager.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/DefaultControlActionsManager.java new file mode 100644 index 0000000..ff048bc --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/DefaultControlActionsManager.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.binding.editors.controls; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds; + +/** + * Default manager for installing/unistalling global handlers for {@link Control} actions commands. + * + * @author sablin_aa + */ +public class DefaultControlActionsManager extends AbstractControlActionsManager { + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public DefaultControlActionsManager(final Control control) { + super(control); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Handlers + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected IHandler getHandlerFor(String actionName) { + if (actionName.equalsIgnoreCase(IWorkbenchActionDefinitionIds.SELECT_ALL)) { + return SELECTALL_HANDLER; + } + return super.getHandlerFor(actionName); + } + + /** + * Handler for process "Select all" action. + */ + private final IHandler SELECTALL_HANDLER = new AbstractHandler() { + public Object execute(ExecutionEvent event) throws ExecutionException { + selectAllExecuted(); + return null; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean isHandled() { + return true; + } + }; + + protected void selectAllExecuted() { + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/check/Assert.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/check/Assert.java new file mode 100644 index 0000000..28de0b6 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/check/Assert.java @@ -0,0 +1,361 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.check; + +import java.text.MessageFormat; + +/** + * Assert is useful for for embedding runtime sanity checks in code. The predicate + * methods all test a condition and throw some type of unchecked exception if the condition does not + * hold. + *

+ * Assertion failure exceptions, like most runtime exceptions, are thrown when something is + * misbehaving. Assertion failures are invariably unspecified behavior; consequently, clients should + * never rely on these being thrown (and certainly should not being catching them specifically). + * + * @author scheglov_ke + * @coverage core.util + */ +public final class Assert { + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + private Assert() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // "legal" + // + //////////////////////////////////////////////////////////////////////////// + /** + * Asserts that an argument is legal. If the given boolean is not true, an + * IllegalArgumentException is thrown. + * + * @param expression + * the boolean expression of the check + * @return true if the check passes (does not return if the check fails) + * @exception IllegalArgumentException + * if the legality test failed + */ + public static boolean isLegal(boolean expression) { + return isLegal(expression, ""); //$NON-NLS-1$ + } + + /** + * Asserts that an argument is legal. If the given boolean is not true, an + * IllegalArgumentException is thrown. The given message is included in that + * exception, to aid debugging. + * + * @param expression + * the boolean expression of the check + * @param message + * the message to include in the exception + * @return true if the check passes (does not return if the check fails) + * @exception IllegalArgumentException + * if the legality test failed + */ + public static boolean isLegal(boolean expression, String message) { + if (!expression) { + throw new IllegalArgumentException(message); + } + return expression; + } + + //////////////////////////////////////////////////////////////////////////// + // + // "null" + // + //////////////////////////////////////////////////////////////////////////// + /** + * Asserts that the given object is null. If this is not the case, some kind of + * unchecked exception is thrown. + * + * @param object + * the value to test + */ + public static void isNull(Object object) { + isNull(object, ""); //$NON-NLS-1$ + } + + /** + * Asserts that the given object is null. If this is not the case, some kind of + * unchecked exception is thrown. The given message is included in that exception, to aid + * debugging. + * + * @param object + * the value to test + * @param message + * the message to include in the exception + */ + public static void isNull(Object object, String message) { + if (object != null) { + throw new AssertionFailedException("null argument expected: " + message); //$NON-NLS-1$ + } + } + + /** + * Asserts that the given object is not null. If this is not the case, some kind of + * unchecked exception is thrown. The given message is included in that exception, to aid + * debugging. + * + * @param object + * the value to test + * @param errorFormat + * the format of error message to produce if the check fails, as expected by + * {@link String#format(String, Object...)}. For example + * "Execution flow problem. %s expected, but %s found.". + * @param args + * the arguments for {@code errorFormat} + */ + public static void isNull(Object object, String errorFormat, Object... args) { + if (object != null) { + fail("null argument expected: " + String.format(errorFormat, args)); //$NON-NLS-1$ + } + } + + /** + * @param errorFormat + * the format of error message suitable for {@link MessageFormat}. + * @param errorFormat + * the format of error message to produce if the check fails, as expected by + * {@link MessageFormat}. For example + * "Execution flow problem. {0} expected, but {1} found.". + */ + public static void isNull2(Object object, String errorFormat, Object... args) { + if (object != null) { + String message = "null argument expected: " + MessageFormat.format(errorFormat, args); //$NON-NLS-1$ + fail(message); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // not "null" + // + //////////////////////////////////////////////////////////////////////////// + /** + * Asserts that the given object is not null. If this is not the case, some kind of + * unchecked exception is thrown. + * + * @param object + * the value to test + */ + public static void isNotNull(Object object) { + isNotNull(object, ""); //$NON-NLS-1$ + } + + /** + * Asserts that the given object is not null. If this is not the case, some kind of + * unchecked exception is thrown. The given message is included in that exception, to aid + * debugging. + * + * @param object + * the value to test + * @param message + * the message to include in the exception + */ + public static void isNotNull(Object object, String message) { + if (object == null) { + fail("null argument: " + message); //$NON-NLS-1$ + } + } + + /** + * Asserts that the given object is not null. If this is not the case, some kind of + * unchecked exception is thrown. The given message is included in that exception, to aid + * debugging. + * + * @param object + * the value to test + * @param errorFormat + * the format of error message to produce if the check fails, as expected by + * {@link String#format(String, Object...)}. For example + * "Execution flow problem. %s expected, but %s found.". + * @param args + * the arguments for {@code errorFormat} + */ + public static void isNotNull(Object object, String errorFormat, Object... args) { + if (object == null) { + fail("null argument: " + String.format(errorFormat, args)); //$NON-NLS-1$ + } + } + + /** + * @param errorFormat + * the format of error message suitable for {@link MessageFormat}. + * @param errorFormat + * the format of error message to produce if the check fails, as expected by + * {@link MessageFormat}. For example + * "Execution flow problem. {0} expected, but {1} found.". + */ + public static void isNotNull2(Object object, String errorFormat, Object... args) { + if (object == null) { + String message = "null argument: " + MessageFormat.format(errorFormat, args); //$NON-NLS-1$ + fail(message); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Fail + // + //////////////////////////////////////////////////////////////////////////// + /** + * Fails with given message. + * + * @param message + * the message to include in the exception + */ + public static void fail(String message) { + throw new AssertionFailedException(message); + } + + /** + * @param errorFormat + * the format of error message to produce if the check fails, as expected by + * {@link MessageFormat}. For example "{0} expected, but {1} found.". + */ + public static void fail(String errorFormat, Object... args) { + String message = MessageFormat.format(errorFormat, args); + throw new AssertionFailedException(message); + } + + //////////////////////////////////////////////////////////////////////////// + // + // "true" + // + //////////////////////////////////////////////////////////////////////////// + /** + * Asserts that the given boolean is true. If this is not the case, some kind of + * unchecked exception is thrown. + * + * @param expression + * the boolean expression of the check + * @return true if the check passes (does not return if the check fails) + */ + public static boolean isTrue(boolean expression) { + return isTrue(expression, ""); //$NON-NLS-1$ + } + + /** + * Asserts that the given boolean is true. If this is not the case, some kind of + * unchecked exception is thrown. The given message is included in that exception, to aid + * debugging. + * + * @param expression + * the boolean expression of the check + * @param message + * the message to include in the exception + * @return true if the check passes (does not return if the check fails) + */ + public static boolean isTrue(boolean expression, String message) { + if (!expression) { + fail("assertion failed: " + message); //$NON-NLS-1$ + } + return expression; + } + + /** + * Asserts that the given boolean is true. If this is not the case, some kind of + * unchecked exception is thrown. The given message is included in that exception, to aid + * debugging. + * + * @param expression + * the boolean expression of the check + * @param errorFormat + * the format of error message to produce if the check fails, as expected by + * {@link String#format(String, Object...)}. For example + * "Execution flow problem. %s expected, but %s found.". + * @param args + * the arguments for {@code errorFormat} + * @return true if the check passes (does not return if the check fails) + */ + public static boolean isTrue(boolean expression, String errorFormat, Object... args) { + if (!expression) { + fail("assertion failed: " + String.format(errorFormat, args)); //$NON-NLS-1$ + } + return expression; + } + + /** + * Asserts that the given boolean is true. If this is not the case, some kind of + * unchecked exception is thrown. The given message is included in that exception, to aid + * debugging. + * + * @param expression + * the boolean expression to check. + * @param errorFormat + * the format of error message to produce if the check fails, as expected by + * {@link MessageFormat}. For example "{0} expected, but {1} found.". + */ + public static boolean isTrue2(boolean expression, String errorFormat, Object... args) { + if (!expression) { + fail(errorFormat, args); + } + return expression; + } + + //////////////////////////////////////////////////////////////////////////// + // + // equals + // + //////////////////////////////////////////////////////////////////////////// + /** + * Asserts that given actual value equals expected value. If this is not the case, some kind of + * unchecked exception is thrown. + * + * @param expected + * the expected value + * @param the + * actual value to check + */ + public static void equals(int expected, int actual) { + equals(expected, actual, expected + " expected, but " + actual + " found"); + } + + /** + * Asserts that given actual value equals expected value. If this is not the case, some kind of + * unchecked exception is thrown. The given message is included in that exception, to aid + * debugging. + * + * @param expected + * the expected value + * @param the + * actual value to check + * @param message + * the message to include in the exception + */ + public static void equals(int expected, int actual, String message) { + if (expected != actual) { + fail("assertation failed: " + message); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // instanceOf + // + //////////////////////////////////////////////////////////////////////////// + /** + * Asserts that given object is not null and has class compatible with given. + */ + public static void instanceOf(Class expectedClass, Object o) { + if (o == null) { + fail(expectedClass.getName() + " expected, but 'null' found."); + } + if (!expectedClass.isAssignableFrom(o.getClass())) { + fail(expectedClass.getName() + " expected, but " + o.getClass().getName() + " found."); + } + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/check/AssertionFailedException.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/check/AssertionFailedException.java new file mode 100644 index 0000000..d0c9794 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/check/AssertionFailedException.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.check; + +/** + * AssertionFailedException is a runtime exception thrown by some of the methods in + * Assert. + * + * @author scheglov_ke + * @coverage core.util + */ +public final class AssertionFailedException extends RuntimeException { + private static final long serialVersionUID = 0L; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + /** + * Constructs a new exception with the given message. + * + * @param detail + * the message + */ + public AssertionFailedException(String detail) { + super(detail); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/ExecutionUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/ExecutionUtils.java new file mode 100644 index 0000000..d80ff94 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/ExecutionUtils.java @@ -0,0 +1,292 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.execution; + +import org.eclipse.swt.widgets.Display; +import org.eclipse.wb.internal.core.DesignerPlugin; +import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils; + +import java.beans.Beans; + +/** + * Utilities for executing actions, such as {@link RunnableEx}. + * + * @author scheglov_ke + * @coverage core.util + */ +public class ExecutionUtils { + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + private ExecutionUtils() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Sleep + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sleeps given number of milliseconds, ignoring exceptions. + */ + public static void sleep(final int millis) { + runIgnore(new RunnableEx() { + @Override + public void run() throws Exception { + Thread.sleep(millis); + } + }); + } + + /** + * Waits given number of milliseconds and runs events loop every 1 millisecond. At least one + * events loop will be executed. If current thread is not UI thread, then this method works just + * as {@link #sleep(int)}. + */ + public static void waitEventLoop(int millis) { + Display display = Display.getCurrent(); + if (display != null) { + long nanos = millis * 1000000L; + long start = System.nanoTime(); + do { + sleep(0); + while (display.readAndDispatch()) { + // do nothing + } + } while (System.nanoTime() - start < nanos); + } else { + sleep(millis); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // void + // + //////////////////////////////////////////////////////////////////////////// + /** + * Runs given {@link RunnableEx} and ignores exceptions. + * + * @return true if execution was finished without exception. + */ + public static boolean runIgnore(RunnableEx runnable) { + try { + runnable.run(); + return true; + } catch (Throwable e) { + return false; + } + } + + /** + * Runs given {@link RunnableEx} and logs exceptions using {@link DesignerPlugin#log(Throwable)}. + * + * @return true if execution was finished without exception. + */ + public static boolean runLog(RunnableEx runnable) { + try { + runnable.run(); + return true; + } catch (Throwable e) { + DesignerPlugin.log(e); + return false; + } + } + + /** + * Runs given {@link RunnableEx} and re-throws exceptions using {@link RuntimeException}. + */ + public static void runRethrow(RunnableEx runnable) { + try { + runnable.run(); + } catch (Throwable e) { + throw ReflectionUtils.propagate(e); + } + } + + /** + * Runs given {@link RunnableEx} and re-throws exceptions using {@link RuntimeException}. + */ + public static void runRethrow(RunnableEx runnable, String format, Object... args) { + try { + runnable.run(); + } catch (Throwable e) { + String message = String.format(format, args); + throw new RuntimeException(message, e); + } + } + + /** + * Ensures that {@link Beans#isDesignTime()} returns true and runs given + * {@link RunnableEx}. + */ + public static void runDesignTime(RunnableEx runnable) throws Exception { + boolean old_designTime = Beans.isDesignTime(); + try { + Beans.setDesignTime(true); + runnable.run(); + } finally { + Beans.setDesignTime(old_designTime); + } + } + + /** + * Ensures that {@link Beans#isDesignTime()} returns true and runs given + * {@link RunnableEx}. + */ + public static T runDesignTime(RunnableObjectEx runnable) throws Exception { + boolean old_designTime = Beans.isDesignTime(); + try { + Beans.setDesignTime(true); + return runnable.runObject(); + } finally { + Beans.setDesignTime(old_designTime); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // UI + // + //////////////////////////////////////////////////////////////////////////// + /** + * Runs given {@link RunnableEx} inside of UI thread, using {@link Display#syncExec(Runnable)}. + * + * @return true if {@link RunnableEx} was executed without any {@link Exception}. + */ + public static boolean runLogUI(final RunnableEx runnable) { + final boolean[] success = new boolean[1]; + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + success[0] = ExecutionUtils.runLog(runnable); + } + }); + return success[0]; + } + + /** + * Runs given {@link RunnableEx} inside of UI thread, using {@link Display#syncExec(Runnable)}. + */ + public static void runRethrowUI(final RunnableEx runnable) { + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + ExecutionUtils.runRethrow(runnable); + } + }); + } + + /** + * Runs given {@link RunnableEx} within UI thread using {@link Display#asyncExec(Runnable)}. Logs + * a {@link Throwable} which may occur. + */ + public static void runAsync(final RunnableEx runnable) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + ExecutionUtils.runLog(runnable); + } + }); + } + + /** + * Runs given {@link RunnableEx} inside of UI thread, using {@link Display#syncExec(Runnable)}. + */ + @SuppressWarnings("unchecked") + public static T runObjectUI(final RunnableObjectEx runnable) { + final Object[] result = new Object[1]; + runRethrowUI(new RunnableEx() { + @Override + public void run() throws Exception { + result[0] = runObject(runnable); + } + }); + return (T) result[0]; + } + + /** + * Runs given {@link RunnableEx} as {@link #runLog(RunnableEx)}, but using + * {@link Display#asyncExec(Runnable)}. + */ + public static void runLogLater(final RunnableEx runnable) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + ExecutionUtils.runLog(runnable); + } + }); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Object + // + //////////////////////////////////////////////////////////////////////////// + /** + * Runs given {@link RunnableEx} and re-throws exceptions using {@link RuntimeException}. + * + * @return the {@link Object} returned by {@link RunnableEx#run()}. + */ + public static T runObject(RunnableObjectEx runnable) { + try { + return runnable.runObject(); + } catch (Throwable e) { + throw ReflectionUtils.propagate(e); + } + } + + /** + * Runs given {@link RunnableEx} and re-throws exceptions using {@link RuntimeException}. + * + * @return the {@link Object} returned by {@link RunnableEx#run()}. + */ + public static T runObject(RunnableObjectEx runnable, String format, Object... args) { + try { + return runnable.runObject(); + } catch (Throwable e) { + String message = String.format(format, args); + throw new Error(message, e); + } + } + + /** + * Runs given {@link RunnableEx} and ignores exceptions. + * + * @return the {@link Object} returned by {@link RunnableEx#run()} or defaultValue if + * exception happened. + */ + public static T runObjectIgnore(RunnableObjectEx runnable, T defaultValue) { + try { + return runnable.runObject(); + } catch (Throwable e) { + return defaultValue; + } + } + + /** + * Runs given {@link RunnableEx} and logs exceptions using {@link DesignerPlugin#log(Throwable)}. + * + * @return the {@link Object} returned by {@link RunnableEx#run()} or defaultValue if + * exception was logged. + */ + public static T runObjectLog(RunnableObjectEx runnable, T defaultValue) { + try { + return runnable.runObject(); + } catch (Throwable e) { + DesignerPlugin.log(e); + return defaultValue; + } + } + +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableEx.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableEx.java new file mode 100644 index 0000000..18c141f --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableEx.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.execution; + +/** + * Analog of {@link Runnable} where method run can throw {@link Exception}. + * + * @author scheglov_ke + * @coverage core.util + */ +public interface RunnableEx { + /** + * Executes operation that can cause {@link Exception}. + */ + void run() throws Exception; +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableObjectEx.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableObjectEx.java new file mode 100644 index 0000000..02a2865 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableObjectEx.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.execution; + +/** + * Analog of {@link Runnable} where method run can throw {@link Exception}. + * + * @author scheglov_ke + * @coverage core.util + */ +public interface RunnableObjectEx { + /** + * Executes operation that can cause {@link Exception}. + * + * @return some {@link Object} result for caller. + */ + T runObject() throws Exception; +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassLoaderLocalMap.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassLoaderLocalMap.java new file mode 100644 index 0000000..b74ba88 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassLoaderLocalMap.java @@ -0,0 +1,192 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.reflect; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Helper for setting properties for {@link ClassLoader}. + *

+ * http://java.dzone.com/articles/classloaderlocal-how-avoid + * + * @author Jevgeni Kabanov + * @author scheglov_ke + * @coverage core.util + */ +@SuppressWarnings("unchecked") +public class ClassLoaderLocalMap implements Opcodes { + private static final String NAME = "GEN$$ClassLoaderProperties"; + private static final Map globalMap = + Collections.synchronizedMap(new WeakHashMap()); + private static Method defineMethod; + private static Method findLoadedClass; + static { + try { + defineMethod = + ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{ + String.class, + byte[].class, + int.class, + int.class}); + defineMethod.setAccessible(true); + findLoadedClass = + ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[]{String.class}); + findLoadedClass.setAccessible(true); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Map + // + //////////////////////////////////////////////////////////////////////////// + public static boolean containsKey(ClassLoader cl, Object key) { + if (cl == null) { + return globalMap.containsKey(key); + } + // synchronizing over ClassLoader is usually safest + synchronized (cl) { + if (!hasHolder(cl)) { + return false; + } + return getLocalMap(cl).containsKey(key); + } + } + + public static void put(ClassLoader cl, Object key, Object value) { + if (cl == null) { + globalMap.put(key, value); + return; + } + // synchronizing over ClassLoader is usually safest + synchronized (cl) { + getLocalMap(cl).put(key, value); + } + } + + public static Object get(ClassLoader cl, Object key) { + if (cl == null) { + return globalMap.get(key); + } + // synchronizing over ClassLoader is usually safest + synchronized (cl) { + return getLocalMap(cl).get(key); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Implementation + // + //////////////////////////////////////////////////////////////////////////// + private static boolean hasHolder(ClassLoader cl) { + String propertiesClassName = NAME; + try { + Class clazz = (Class) findLoadedClass.invoke(cl, new Object[]{propertiesClassName}); + if (clazz == null) { + return false; + } + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getTargetException()); + } + return true; + } + + private static Map getLocalMap(ClassLoader cl) { + String holderClassName = NAME; + Class holderClass; + try { + holderClass = (Class) findLoadedClass.invoke(cl, new Object[]{holderClassName}); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getTargetException()); + } + if (holderClass == null) { + byte[] classBytes = buildHolderByteCode(holderClassName); + try { + holderClass = + (Class) defineMethod.invoke( + cl, + new Object[]{ + holderClassName, + classBytes, + Integer.valueOf(0), + Integer.valueOf(classBytes.length)}); + } catch (InvocationTargetException e1) { + throw new RuntimeException(e1.getTargetException()); + } catch (Throwable e1) { + throw new RuntimeException(e1); + } + } + try { + return (Map) holderClass.getDeclaredField("localMap").get(null); + } catch (Throwable e1) { + throw new RuntimeException(e1); + } + } + + private static byte[] buildHolderByteCode(String holderClassName) { + ClassWriter cw = new ClassWriter(0); + FieldVisitor fv; + MethodVisitor mv; + cw.visit(V1_2, ACC_PUBLIC + ACC_SUPER, holderClassName, null, "java/lang/Object", null); + { + fv = + cw.visitField( + ACC_PUBLIC + ACC_FINAL + ACC_STATIC, + "localMap", + "Ljava/util/Map;", + null, + null); + fv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null); + mv.visitCode(); + mv.visitTypeInsn(NEW, "java/util/WeakHashMap"); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, "java/util/WeakHashMap", "", "()V"); + mv.visitFieldInsn(PUTSTATIC, holderClassName, "localMap", "Ljava/util/Map;"); + mv.visitInsn(RETURN); + mv.visitMaxs(2, 0); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + cw.visitEnd(); + return cw.toByteArray(); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassMap.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassMap.java new file mode 100644 index 0000000..ce0d339 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassMap.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.reflect; + +import java.util.HashMap; +import java.util.Map; + +/** + * {@link Map}-like interface for mapping {@link Class} to value. + * + * @author scheglov_ke + * @coverage core.util + */ +public final class ClassMap { + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + /** + * Creates new instance of {@link ClassMap}. + */ + public static ClassMap create() { + return new ClassMap(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Map + // + //////////////////////////////////////////////////////////////////////////// + public void put(Class key, V value) { + getMap(key).put(key, value); + } + + public V get(Class key) { + return getMap(key).get(key); + } + + public void remove(Class key) { + getMap(key).remove(key); + } + + public void clear(ClassLoader classLoader) { + getMap(classLoader).clear(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Implementation + // + //////////////////////////////////////////////////////////////////////////// + private Map, V> getMap(Class key) { + ClassLoader classLoader = key.getClassLoader(); + return getMap(classLoader); + } + + @SuppressWarnings("unchecked") + private Map, V> getMap(ClassLoader classLoader) { + Object map = ClassLoaderLocalMap.get(classLoader, this); + if (map == null) { + map = new HashMap, V>(); + ClassLoaderLocalMap.put(classLoader, this, map); + } + return (Map, V>) map; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ReflectionUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ReflectionUtils.java new file mode 100644 index 0000000..b362211 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ReflectionUtils.java @@ -0,0 +1,327 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.reflect; + +import com.google.common.collect.Maps; + +import org.eclipse.wb.internal.core.utils.check.Assert; + +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Map; + +/** + * Contains different Java reflection utilities. + * + * @author scheglov_ke + * @coverage core.util + */ +public class ReflectionUtils { + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + private ReflectionUtils() { + } + + //////////////////////////////////////////////////////////////////////////// + // + // Signature + // + //////////////////////////////////////////////////////////////////////////// + /** + * @param runtime + * is true if we need name for class loading, false if we need + * name for source generation. + * + * @return the fully qualified name of given {@link Type}. + */ + public static String getFullyQualifiedName(Type type, boolean runtime) { + Assert.isNotNull(type); + // Class + if (type instanceof Class) { + Class clazz = (Class) type; + // array + if (clazz.isArray()) { + return getFullyQualifiedName(clazz.getComponentType(), runtime) + "[]"; + } + // object + String name = clazz.getName(); + if (!runtime) { + name = name.replace('$', '.'); + } + return name; + } + // GenericArrayType + if (type instanceof GenericArrayType) { + GenericArrayType genericArrayType = (GenericArrayType) type; + return getFullyQualifiedName(genericArrayType.getGenericComponentType(), runtime) + "[]"; + } + // ParameterizedType + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type rawType = parameterizedType.getRawType(); + // raw type + StringBuilder sb = new StringBuilder(); + sb.append(getFullyQualifiedName(rawType, runtime)); + // type arguments + sb.append("<"); + boolean firstTypeArgument = true; + for (Type typeArgument : parameterizedType.getActualTypeArguments()) { + if (!firstTypeArgument) { + sb.append(","); + } + firstTypeArgument = false; + sb.append(getFullyQualifiedName(typeArgument, runtime)); + } + sb.append(">"); + // done + return sb.toString(); + } + // WildcardType + if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + return "? extends " + getFullyQualifiedName(wildcardType.getUpperBounds()[0], runtime); + } + // TypeVariable + TypeVariable typeVariable = (TypeVariable) type; + return typeVariable.getName(); + } + + /** + * Appends fully qualified names of given parameter types (appends also "()"). + */ + private static void appendParameterTypes(StringBuilder buffer, Type[] parameterTypes) { + buffer.append('('); + boolean firstParameter = true; + for (Type parameterType : parameterTypes) { + if (firstParameter) { + firstParameter = false; + } else { + buffer.append(','); + } + buffer.append(getFullyQualifiedName(parameterType, false)); + } + buffer.append(')'); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Method + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return all declared {@link Method}'s, including protected and private. + */ + public static Map getMethods(Class clazz) { + Map methods = Maps.newHashMap(); + // process classes + for (Class c = clazz; c != null; c = c.getSuperclass()) { + for (Method method : c.getDeclaredMethods()) { + String signature = getMethodSignature(method); + if (!methods.containsKey(signature)) { + method.setAccessible(true); + methods.put(signature, method); + } + } + } + // process interfaces + for (Class interfaceClass : clazz.getInterfaces()) { + for (Method method : interfaceClass.getDeclaredMethods()) { + String signature = getMethodSignature(method); + if (!methods.containsKey(signature)) { + method.setAccessible(true); + methods.put(signature, method); + } + } + } + // done + return methods; + } + + /** + * @return signature for given {@link Method}. This signature is not same signature as in JVM or + * JDT, just some string that unique identifies method in its {@link Class}. + */ + public static String getMethodSignature(Method method) { + Assert.isNotNull(method); + return getMethodSignature(method.getName(), method.getParameterTypes()); + } + + /** + * Returns the signature of {@link Method} with given combination of name and parameter types. + * This signature is not same signature as in JVM or JDT, just some string that unique identifies + * method in its {@link Class}. + * + * @param name + * the name of {@link Method}. + * @param parameterTypes + * the types of {@link Method} parameters. + * + * @return signature of {@link Method}. + */ + public static String getMethodSignature(String name, Type... parameterTypes) { + Assert.isNotNull(name); + Assert.isNotNull(parameterTypes); + // + StringBuilder buffer = new StringBuilder(); + buffer.append(name); + appendParameterTypes(buffer, parameterTypes); + return buffer.toString(); + } + + private static final ClassMap> m_getMethodBySignature = ClassMap.create(); + + /** + * Returns the {@link Method} defined in {@link Class}. This method can have any visibility, i.e. + * we can find even protected/private methods. Can return null if no method with + * given signature found. + * + * @param clazz + * the {@link Class} to get method from it, or its superclass. + * @param signature + * the signature of method in same format as {@link #getMethodSignature(Method)}. + * + * @return the {@link Method} for given signature, or null if no such method found. + */ + public static Method getMethodBySignature(Class clazz, String signature) { + Assert.isNotNull(clazz); + Assert.isNotNull(signature); + // prepare cache + Map cache = m_getMethodBySignature.get(clazz); + if (cache == null) { + cache = getMethods(clazz); + m_getMethodBySignature.put(clazz, cache); + } + // use cache + return cache.get(signature); + } + + /** + * @return the {@link Object} result of invoking method with given signature. + */ + public static Object invokeMethod(Object object, String signature, Object... arguments) + throws Exception { + Assert.isNotNull(object); + Assert.isNotNull(arguments); + // prepare class/object + Class refClass = getRefClass(object); + Object refObject = getRefObject(object); + // prepare method + Method method = getMethodBySignature(refClass, signature); + Assert.isNotNull(method, "Can not find method " + signature + " in " + refClass); + // do invoke + try { + return method.invoke(refObject, arguments); + } catch (InvocationTargetException e) { + throw propagate(e.getCause()); + } + } + + /** + * Invokes method by name and parameter types. + * + * @param object + * the object to call, may be {@link Class} for invoking static method. + * @param name + * the name of method. + * @param parameterTypes + * the types of parameters. + * @param arguments + * the values of argument for invocation. + * + * @return the {@link Object} result of invoking method. + */ + public static Object invokeMethod2(Object object, + String name, + Class[] parameterTypes, + Object[] arguments) throws Exception { + Assert.equals(parameterTypes.length, arguments.length); + String signature = getMethodSignature(name, parameterTypes); + return invokeMethod(object, signature, arguments); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Utils + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the {@link Class} of given {@link Object} or casted object, if it is {@link Class} + * itself. + */ + private static Class getRefClass(Object object) { + return object instanceof Class ? (Class) object : object.getClass(); + } + + /** + * @return the {@link Object} that should be used as argument for {@link Field#get(Object)} and + * {@link Method#invoke(Object, Object[])}. + */ + private static Object getRefObject(Object object) { + return object instanceof Class ? null : object; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Throwable propagation + // + //////////////////////////////////////////////////////////////////////////// + /** + * Helper class used in {@link #propagate(Throwable)}. + */ + private static class ExceptionThrower { + private static Throwable throwable; + + private ExceptionThrower() throws Throwable { + if (System.getProperty("wbp.ReflectionUtils.propagate().InstantiationException") != null) { + throw new InstantiationException(); + } + if (System.getProperty("wbp.ReflectionUtils.propagate().IllegalAccessException") != null) { + throw new IllegalAccessException(); + } + throw throwable; + } + + public static synchronized void spit(Throwable t) { + if (System.getProperty("wbp.ReflectionUtils.propagate().dontThrow") == null) { + ExceptionThrower.throwable = t; + try { + ExceptionThrower.class.newInstance(); + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + } finally { + ExceptionThrower.throwable = null; + } + } + } + } + + /** + * Propagates {@code throwable} as-is without any wrapping. This is trick. + * + * @return nothing will ever be returned; this return type is only for your convenience, to use + * this method in "throw" statement. + */ + public static RuntimeException propagate(Throwable throwable) { + if (System.getProperty("wbp.ReflectionUtils.propagate().forceReturn") == null) { + ExceptionThrower.spit(throwable); + } + return null; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java new file mode 100644 index 0000000..f7cc09d --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java @@ -0,0 +1,337 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.ui; + +import com.google.common.io.Closeables; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Display; +import org.eclipse.wb.draw2d.IColorConstants; + +import java.io.InputStream; +import java.net.URL; + +/** + * Utilities for drawing on {@link GC}. + * + * @author scheglov_ke + * @coverage core.ui + */ +public class DrawUtils { + private static final String DOTS = "..."; + + //////////////////////////////////////////////////////////////////////////// + // + // Drawing + // + //////////////////////////////////////////////////////////////////////////// + /** + * Draws given text clipped horizontally and centered vertically. + */ + public static final void drawStringCV(GC gc, String text, int x, int y, int width, int height) { + Rectangle oldClipping = gc.getClipping(); + try { + gc.setClipping(new Rectangle(x, y, width, height)); + // + int textStartY = y + (height - gc.getFontMetrics().getHeight()) / 2; + gc.drawString(clipString(gc, text, width), x, textStartY, true); + } finally { + gc.setClipping(oldClipping); + } + } + + /** + * Draws given text clipped or centered horizontally and centered vertically. + */ + public static final void drawStringCHCV(GC gc, String text, int x, int y, int width, int height) { + int textStartY = y + (height - gc.getFontMetrics().getHeight()) / 2; + Point textSize = gc.stringExtent(text); + // + if (textSize.x > width) { + gc.drawString(clipString(gc, text, width), x, textStartY); + } else { + gc.drawString(text, x + (width - textSize.x) / 2, textStartY); + } + } + + /** + * Draws image at given x and centered vertically. + */ + public static final void drawImageCV(GC gc, Image image, int x, int y, int height) { + if (image != null) { + Rectangle imageBounds = image.getBounds(); + gc.drawImage(image, x, y + (height - imageBounds.height) / 2); + } + } + + /** + * Draws image at given x and centered vertically. + */ + public static final void drawImageCHCV(GC gc, Image image, int x, int y, int width, int height) { + if (image != null) { + Rectangle imageBounds = image.getBounds(); + int centerX = (width - imageBounds.width) / 2; + int centerY = y + (height - imageBounds.height) / 2; + gc.drawImage(image, x + centerX, centerY); + } + } + + /** + * Draws {@link Image} on {@link GC} centered in given {@link Rectangle}. If {@link Image} is + * bigger that {@link Rectangle}, {@link Image} will be scaled down as needed with keeping + * proportions. + */ + public static void drawScaledImage(GC gc, Image image, Rectangle targetRectangle) { + int imageWidth = image.getBounds().width; + int imageHeight = image.getBounds().height; + // prepare scaled image size + int newImageWidth; + int newImageHeight; + if (imageWidth <= targetRectangle.width && imageHeight <= targetRectangle.height) { + newImageWidth = imageWidth; + newImageHeight = imageHeight; + } else { + // prepare minimal scale + double k; + { + double k_w = targetRectangle.width / (double) imageWidth; + double k_h = targetRectangle.height / (double) imageHeight; + k = Math.min(k_w, k_h); + } + // calculate scaled image size + newImageWidth = (int) (imageWidth * k); + newImageHeight = (int) (imageHeight * k); + } + // draw image centered in target rectangle + int destX = targetRectangle.x + (targetRectangle.width - newImageWidth) / 2; + int destY = targetRectangle.y + (targetRectangle.height - newImageHeight) / 2; + gc.drawImage(image, 0, 0, imageWidth, imageHeight, destX, destY, newImageWidth, newImageHeight); + } + + /** + * @return the string clipped to have width less than given. Clipping is done as trailing "...". + */ + public static String clipString(GC gc, String text, int width) { + if (width <= 0) { + return ""; + } + // check if text already fits in given width + if (gc.stringExtent(text).x <= width) { + return text; + } + // use average count of characters as base + int count = Math.min(width / gc.getFontMetrics().getAverageCharWidth(), text.length()); + if (gc.stringExtent(text.substring(0, count) + DOTS).x > width) { + while (count > 0 && gc.stringExtent(text.substring(0, count) + DOTS).x > width) { + count--; + } + } else { + while (count < text.length() - 1 + && gc.stringExtent(text.substring(0, count + 1) + DOTS).x < width) { + count++; + } + } + return text.substring(0, count) + DOTS; + } + + /** + * Draws {@link String} in rectangle, wraps at any character (not by words). + */ + public static void drawTextWrap(GC gc, String text, int x, int y, int width, int height) { + int y_ = y; + int x_ = x; + int lineHeight = 0; + for (int i = 0; i < text.length(); i++) { + String c = text.substring(i, i + 1); + Point extent = gc.stringExtent(c); + if (x_ + extent.x > x + width) { + y_ += lineHeight; + if (y_ > y + height) { + return; + } + x_ = x; + } + gc.drawText(c, x_, y_); + x_ += extent.x; + lineHeight = Math.max(lineHeight, extent.y); + } + } + + /** + * Draws 3D highlight rectangle. + */ + public static void drawHighlightRectangle(GC gc, int x, int y, int width, int height) { + int right = x + width - 1; + int bottom = y + height - 1; + // + Color oldForeground = gc.getForeground(); + try { + gc.setForeground(IColorConstants.buttonLightest); + gc.drawLine(x, y, right, y); + gc.drawLine(x, y, x, bottom); + // + gc.setForeground(IColorConstants.buttonDarker); + gc.drawLine(right, y, right, bottom); + gc.drawLine(x, bottom, right, bottom); + } finally { + gc.setForeground(oldForeground); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Images + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the {@link Image} loaded relative to given {@link Class}. + */ + public static Image loadImage(Class clazz, String path) { + try { + URL resource = clazz.getResource(path); + if (resource != null) { + InputStream stream = resource.openStream(); + try { + return new Image(null, stream); + } finally { + Closeables.closeQuietly(stream); + } + } + } catch (Throwable e) { + } + return null; + } + + /** + * @return the thumbnail {@link Image} of required size for given "big" {@link Image}, centered or + * scaled down. + */ + public static Image getThubmnail(Image image, + int minWidth, + int minHeight, + int maxWidth, + int maxHeight) { + Rectangle imageBounds = image.getBounds(); + int imageWidth = imageBounds.width; + int imageHeight = imageBounds.height; + if (imageWidth < minWidth && imageHeight < minHeight) { + // create "thumbnail" Image with required size + Image thumbnail = new Image(null, minWidth, minHeight); + GC gc = new GC(thumbnail); + try { + drawImageCHCV(gc, image, 0, 0, minWidth, minHeight); + } finally { + gc.dispose(); + } + // recreate "thumbnail" Image with transparent pixel + try { + ImageData thumbnailData = thumbnail.getImageData(); + thumbnailData.transparentPixel = thumbnailData.getPixel(0, 0); + return new Image(null, thumbnailData); + } finally { + thumbnail.dispose(); + } + } else if (imageWidth <= maxWidth && imageHeight <= maxHeight) { + return new Image(null, image, SWT.IMAGE_COPY); + } else { + double kX = (double) maxWidth / imageWidth; + double kY = (double) maxHeight / imageHeight; + double k = Math.max(kX, kY); + int dWidth = (int) (imageWidth * k); + int dHeight = (int) (imageHeight * k); + ImageData scaledImageData = image.getImageData().scaledTo(dWidth, dHeight); + return new Image(null, scaledImageData); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Colors + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return new {@link Color} based on given {@link Color} and shifted on given value to make it + * darker or lighter. + */ + public static Color getShiftedColor(Color color, int delta) { + int r = Math.max(0, Math.min(color.getRed() + delta, 255)); + int g = Math.max(0, Math.min(color.getGreen() + delta, 255)); + int b = Math.max(0, Math.min(color.getBlue() + delta, 255)); + return new Color(color.getDevice(), r, g, b); + } + + /** + * @return true if the given color is dark. + */ + public static boolean isDarkColor(Color c) { + int value = + (int) Math.sqrt(c.getRed() + * c.getRed() + * .241 + + c.getGreen() + * c.getGreen() + * .691 + + c.getBlue() + * c.getBlue() + * .068); + return value < 130; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Fonts + // + //////////////////////////////////////////////////////////////////////////// + /** + * @return the bold version of given {@link Font}. + */ + public static Font getBoldFont(Font baseFont) { + FontData[] boldData = getModifiedFontData(baseFont, SWT.BOLD); + return new Font(Display.getCurrent(), boldData); + } + + /** + * @return the italic version of given {@link Font}. + */ + public static Font getBoldItalicFont(Font baseFont) { + FontData[] boldData = getModifiedFontData(baseFont, SWT.BOLD | SWT.ITALIC); + return new Font(Display.getCurrent(), boldData); + } + + /** + * @return the italic version of given {@link Font}. + */ + public static Font getItalicFont(Font baseFont) { + FontData[] boldData = getModifiedFontData(baseFont, SWT.ITALIC); + return new Font(Display.getCurrent(), boldData); + } + + /** + * @return the array of {@link FontData} with the specified style. + */ + private static FontData[] getModifiedFontData(Font baseFont, int style) { + FontData[] baseData = baseFont.getFontData(); + FontData[] styleData = new FontData[baseData.length]; + for (int i = 0; i < styleData.length; i++) { + FontData base = baseData[i]; + styleData[i] = new FontData(base.getName(), base.getHeight(), base.getStyle() | style); + } + return styleData; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridDataFactory.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridDataFactory.java new file mode 100644 index 0000000..4ccda40 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridDataFactory.java @@ -0,0 +1,541 @@ +/******************************************************************************* + * Copyright (c) 2005, 2011 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 + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.ui; + +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Control; + +/** + * This class provides a convienient shorthand for creating and initializing GridData. This offers + * several benefits over creating GridData normal way: + * + *

    + *
  • The same factory can be used many times to create several GridData instances
  • + *
  • The setters on GridDataFactory all return "this", allowing them to be chained
  • + *
  • GridDataFactory uses vector setters (it accepts Points), making it easy to set X and Y values + * together
  • + *
+ * + *

+ * GridDataFactory instances are created using one of the static methods on this class. + *

+ * + *

+ * Example usage: + *

+ * + * + * //////////////////////////////////////////////////////////// + * // Example 1: Typical grid data for a non-wrapping label + * + * // GridDataFactory version + * GridDataFactory.fillDefaults().applyTo(myLabel); + * + * // Equivalent SWT version + * GridData labelData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL); + * myLabel.setLayoutData(labelData); + * + * /////////////////////////////////////////////////////////// + * // Example 2: Typical grid data for a wrapping label + * + * // GridDataFactory version + * GridDataFactory.fillDefaults() + * .align(SWT.FILL, SWT.CENTER) + * .hint(150, SWT.DEFAULT) + * .grab(true, false) + * .applyTo(wrappingLabel); + * + * // Equivalent SWT version + * GridData wrappingLabelData = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_CENTER); + * wrappingLabelData.minimumWidth = 1; + * wrappingLabelData.widthHint = 150; + * wrappingLabel.setLayoutData(wrappingLabelData); + * + * ////////////////////////////////////////////////////////////// + * // Example 3: Typical grid data for a scrollable control (a list box, tree, table, etc.) + * + * // GridDataFactory version + * GridDataFactory.fillDefaults().grab(true, true).hint(150, 150).applyTo(listBox); + * + * // Equivalent SWT version + * GridData listBoxData = new GridData(GridData.FILL_BOTH); + * listBoxData.widthHint = 150; + * listBoxData.heightHint = 150; + * listBoxData.minimumWidth = 1; + * listBoxData.minimumHeight = 1; + * listBox.setLayoutData(listBoxData); + * + * ///////////////////////////////////////////////////////////// + * // Example 4: Typical grid data for a button + * + * // GridDataFactory version + * Point preferredSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, false); + * Point hint = Geometry.max(LayoutConstants.getMinButtonSize(), preferredSize); + * GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).hint(hint).applyTo(button); + * + * // Equivalent SWT version + * Point preferredSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, false); + * Point hint = Geometry.max(LayoutConstants.getMinButtonSize(), preferredSize); + * GridData buttonData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER); + * buttonData.widthHint = hint.x; + * buttonData.heightHint = hint.y; + * button.setLayoutData(buttonData); + * + * + *

+ * IMPORTANT: WHEN ASSIGNING LAYOUT DATA TO A CONTROL, BE SURE TO USE + * gridDataFactory.applyTo(control) AND NEVER control.setLayoutData(gridDataFactory). + *

+ * + * @since 3.2 + */ +public final class GridDataFactory { + private final Control m_control; + private final PixelConverter m_pixelConverter; + private final GridData m_data; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + private GridDataFactory(Control control, GridData gridData) { + m_control = control; + m_pixelConverter = new PixelConverter(m_control); + // + m_data = gridData; + if (m_control.getLayoutData() != m_data) { + m_control.setLayoutData(m_data); + } + } + + /** + * Creates new {@link GridDataFactory} with new {@link GridData}. + */ + public static GridDataFactory create(Control control) { + return new GridDataFactory(control, new GridData()); + } + + /** + * Creates new {@link GridDataFactory} for modifying {@link GridData} already installed in + * control. + */ + public static GridDataFactory modify(Control control) { + GridData gridData; + { + Object existingLayoutData = control.getLayoutData(); + if (existingLayoutData instanceof GridData) { + gridData = (GridData) existingLayoutData; + } else { + gridData = new GridData(); + } + } + return new GridDataFactory(control, gridData); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Span + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sets the GridData span. The span controls how many cells are filled by the control. + * + * @param hSpan + * number of columns spanned by the control + * @param vSpan + * number of rows spanned by the control + * @return this + */ + public GridDataFactory span(int hSpan, int vSpan) { + m_data.horizontalSpan = hSpan; + m_data.verticalSpan = vSpan; + return this; + } + + /** + * Sets the GridData span. The span controls how many cells are filled by the control. + * + * @param hSpan + * number of columns spanned by the control + * @return this + */ + public GridDataFactory spanH(int hSpan) { + m_data.horizontalSpan = hSpan; + return this; + } + + /** + * Sets the GridData span. The span controls how many cells are filled by the control. + * + * @param vSpan + * number of rows spanned by the control + * @return this + */ + public GridDataFactory spanV(int vSpan) { + m_data.verticalSpan = vSpan; + return this; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Hint + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sets the width and height hints. The width and height hints override the control's preferred + * size. If either hint is set to SWT.DEFAULT, the control's preferred size is used. + * + * @param xHint + * horizontal hint (pixels), or SWT.DEFAULT to use the control's preferred size + * @param yHint + * vertical hint (pixels), or SWT.DEFAULT to use the control's preferred size + * @return this + */ + public GridDataFactory hint(int xHint, int yHint) { + m_data.widthHint = xHint; + m_data.heightHint = yHint; + return this; + } + + /** + * Sets hint in chars. + */ + public GridDataFactory hintC(int xHintInChars, int yHintInChars) { + hintHC(xHintInChars); + hintVC(yHintInChars); + return this; + } + + /** + * Sets the width hint. + * + * @return this + */ + public GridDataFactory hintH(int xHint) { + m_data.widthHint = xHint; + return this; + } + + /** + * Sets the width hint to the minimum of current hint and given otherHint. + * + * @return this + */ + public GridDataFactory hintHMin(int otherHint) { + m_data.widthHint = Math.min(m_data.widthHint, otherHint); + return this; + } + + /** + * Sets the width hint in chars. + * + * @return this + */ + public GridDataFactory hintHC(int hintInChars) { + return hintH(m_pixelConverter.convertWidthInCharsToPixels(hintInChars)); + } + + /** + * Sets the width hint. + * + * @return this + */ + public GridDataFactory hintHU(int hintInDLU) { + return hintH(m_pixelConverter.convertHorizontalDLUsToPixels(hintInDLU)); + } + + /** + * Sets the height hint. + * + * @return this + */ + public GridDataFactory hintV(int yHint) { + m_data.heightHint = yHint; + return this; + } + + /** + * Sets the height hint in chars. + * + * @return this + */ + public GridDataFactory hintVC(int hintInChars) { + return hintV(m_pixelConverter.convertHeightInCharsToPixels(hintInChars)); + } + + /** + * Increments horizontal hint on given value. + * + * @return this + */ + public GridDataFactory hintHAdd(int increment) { + return hintV(m_data.widthHint + increment); + } + + /** + * Increments vertical hint on given value. + * + * @return this + */ + public GridDataFactory hintVAdd(int increment) { + return hintV(m_data.heightHint + increment); + } + + /** + * Sets the width and height hints. The width and height hints override the control's preferred + * size. If either hint is set to SWT.DEFAULT, the control's preferred size is used. + * + * @param hint + * size (pixels) to be used instead of the control's preferred size. If the x or y values + * are set to SWT.DEFAULT, the control's computeSize() method will be used to obtain that + * dimension of the preferred size. + * @return this + */ + public GridDataFactory hint(Point hint) { + m_data.widthHint = hint.x; + m_data.heightHint = hint.y; + return this; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Minimum size + // + //////////////////////////////////////////////////////////////////////////// + public GridDataFactory minH(int minimumWidth) { + m_data.minimumWidth = minimumWidth; + return this; + } + + public GridDataFactory minHC(int widthInChars) { + return minH(m_pixelConverter.convertWidthInCharsToPixels(widthInChars)); + } + + public GridDataFactory minV(int minimumHeight) { + m_data.minimumHeight = minimumHeight; + return this; + } + + public GridDataFactory minVC(int heightInChars) { + return minV(m_pixelConverter.convertHeightInCharsToPixels(heightInChars)); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Alignment + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sets the alignment of the control within its cell. + * + * @param hAlign + * horizontal alignment. One of SWT.BEGINNING, SWT.CENTER, SWT.END, or SWT.FILL. + * @param vAlign + * vertical alignment. One of SWT.BEGINNING, SWT.CENTER, SWT.END, or SWT.FILL. + * @return this + */ + public GridDataFactory align(int hAlign, int vAlign) { + m_data.horizontalAlignment = hAlign; + m_data.verticalAlignment = vAlign; + return this; + } + + /** + * Sets the horizontal and vertical alignment to GridData.FILL. + */ + public GridDataFactory fill() { + return align(GridData.FILL, GridData.FILL); + } + + /** + * Sets the horizontal alignment of the control within its cell. + * + * @param hAlign + * horizontal alignment. One of SWT.BEGINNING, SWT.CENTER, SWT.END, or SWT.FILL. + * @return this + */ + public GridDataFactory alignH(int hAlign) { + m_data.horizontalAlignment = hAlign; + return this; + } + + /** + * Sets the horizontal alignment of the control to GridData.BEGINNING + * + * @return this + */ + public GridDataFactory alignHL() { + return alignH(GridData.BEGINNING); + } + + /** + * Sets the horizontal alignment of the control to GridData.CENTER + * + * @return this + */ + public GridDataFactory alignHC() { + return alignH(GridData.CENTER); + } + + /** + * Sets the horizontal alignment of the control to GridData.FILL + * + * @return this + */ + public GridDataFactory alignHF() { + return alignH(GridData.FILL); + } + + /** + * Sets the horizontal alignment of the control to GridData.FILL + * + * @return this + */ + public GridDataFactory fillH() { + return alignHF(); + } + + /** + * Sets the horizontal alignment of the control to GridData.END + * + * @return this + */ + public GridDataFactory alignHR() { + return alignH(GridData.END); + } + + /** + * Sets the vertical alignment of the control within its cell. + * + * @param vAlign + * vertical alignment. One of SWT.BEGINNING, SWT.CENTER, SWT.END, or SWT.FILL. + * @return this + */ + public GridDataFactory alignV(int vAlign) { + m_data.verticalAlignment = vAlign; + return this; + } + + /** + * Sets the vertical alignment of the control to GridData.BEGINNING + * + * @return this + */ + public GridDataFactory alignVT() { + return alignV(GridData.BEGINNING); + } + + /** + * Sets the vertical alignment of the control to GridData.CENTER + * + * @return this + */ + public GridDataFactory alignVM() { + return alignV(GridData.CENTER); + } + + /** + * Sets the vertical alignment of the control to GridData.FILL + * + * @return this + */ + public GridDataFactory alignVF() { + return alignV(GridData.FILL); + } + + /** + * Sets the vertical alignment of the control to GridData.FILL + * + * @return this + */ + public GridDataFactory fillV() { + return alignVF(); + } + + /** + * Sets the vertical alignment of the control to GridData.END + * + * @return this + */ + public GridDataFactory alignVB() { + return alignV(GridData.END); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Indent + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sets the indent of the control within the cell in pixels. + */ + public GridDataFactory indentH(int hIndent) { + m_data.horizontalIndent = hIndent; + return this; + } + + /** + * Sets the indent of the control within the cell in characters. + */ + public GridDataFactory indentHC(int hIndent) { + m_data.horizontalIndent = m_pixelConverter.convertWidthInCharsToPixels(hIndent); + return this; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Grab + // + //////////////////////////////////////////////////////////////////////////// + /** + * Determines whether extra horizontal or vertical space should be allocated to this control's + * column when the layout resizes. If any control in the column is set to grab horizontal then the + * whole column will grab horizontal space. If any control in the row is set to grab vertical then + * the whole row will grab vertical space. + * + * @param horizontal + * true if the control's column should grow horizontally + * @param vertical + * true if the control's row should grow vertically + * @return this + */ + public GridDataFactory grab(boolean horizontal, boolean vertical) { + m_data.grabExcessHorizontalSpace = horizontal; + m_data.grabExcessVerticalSpace = vertical; + return this; + } + + public GridDataFactory grabH() { + m_data.grabExcessHorizontalSpace = true; + return this; + } + + public GridDataFactory grabV() { + m_data.grabExcessVerticalSpace = true; + return this; + } + + public GridDataFactory grab() { + return grab(true, true); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Exclude + // + //////////////////////////////////////////////////////////////////////////// + public GridDataFactory exclude(boolean value) { + m_data.exclude = value; + return this; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridLayoutFactory.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridLayoutFactory.java new file mode 100644 index 0000000..f6dc58e --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridLayoutFactory.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.ui; + +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Layout; + +/** + * GridLayoutFactory provides a convenient shorthand for creating and initializing GridLayout. + * + * @author scheglov_ke + */ +public final class GridLayoutFactory { + private final GridLayout m_layout; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + private GridLayoutFactory(Composite composite, GridLayout layout) { + m_layout = layout; + composite.setLayout(m_layout); + } + + public static GridLayoutFactory create(Composite composite) { + return new GridLayoutFactory(composite, new GridLayout()); + } + + public static GridLayoutFactory modify(Composite composite) { + Layout layout = composite.getLayout(); + if (layout instanceof GridLayout) { + return new GridLayoutFactory(composite, (GridLayout) layout); + } + return create(composite); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Access + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sets number of columns in {@link GridLayout}. + */ + public GridLayoutFactory columns(int numColumns) { + m_layout.numColumns = numColumns; + return this; + } + + /** + * Specifies whether all columns in the layout will be forced to have the same width. + */ + public GridLayoutFactory equalColumns() { + m_layout.makeColumnsEqualWidth = true; + return this; + } + + /** + * Sets the horizontal margins. + */ + public GridLayoutFactory marginsH(int margins) { + m_layout.marginWidth = margins; + return this; + } + + /** + * Sets the vertical margins. + */ + public GridLayoutFactory marginsV(int margins) { + m_layout.marginHeight = margins; + return this; + } + + /** + * Sets the horizontal/vertical margins. + */ + public GridLayoutFactory margins(int margins) { + m_layout.marginWidth = m_layout.marginHeight = margins; + return this; + } + + /** + * Sets zero horizontal and vertical margins. + */ + public GridLayoutFactory noMargins() { + m_layout.marginWidth = m_layout.marginHeight = 0; + return this; + } + + /** + * Sets zero horizontal and vertical spacing. + */ + public GridLayoutFactory noSpacing() { + m_layout.horizontalSpacing = m_layout.verticalSpacing = 0; + return this; + } + + /** + * Sets horizontal spacing. + */ + public GridLayoutFactory spacingH(int spacing) { + m_layout.horizontalSpacing = spacing; + return this; + } + + /** + * Sets vertical spacing. + */ + public GridLayoutFactory spacingV(int spacing) { + m_layout.verticalSpacing = spacing; + return this; + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/ImageImageDescriptor.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/ImageImageDescriptor.java new file mode 100644 index 0000000..d5fc39f --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/ImageImageDescriptor.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.ui; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; + +/** + * Implementation of {@link ImageDescriptor} for {@link Image} instance. + * + * @author scheglov_ke + * @coverage core.ui + */ +public final class ImageImageDescriptor extends ImageDescriptor { + private final Image m_Image; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public ImageImageDescriptor(Image image) { + m_Image = image; + } + + //////////////////////////////////////////////////////////////////////////// + // + // ImageDescriptor + // + //////////////////////////////////////////////////////////////////////////// + @Override + public ImageData getImageData() { + return m_Image == null ? null : m_Image.getImageData(); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/PixelConverter.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/PixelConverter.java new file mode 100644 index 0000000..1e3699f --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/PixelConverter.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.ui; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.widgets.Control; + +/** + * Helper class for converting DLU and char size into pixels. + * + * Based on code from JDT UI. + * + * @author scheglov_ke + */ +public class PixelConverter { + private final FontMetrics fFontMetrics; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructors + // + //////////////////////////////////////////////////////////////////////////// + public PixelConverter(Control control) { + GC gc = new GC(control); + gc.setFont(control.getFont()); + fFontMetrics = gc.getFontMetrics(); + gc.dispose(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Conversions + // + //////////////////////////////////////////////////////////////////////////// + /** + * see org.eclipse.jface.dialogs.DialogPage#convertHeightInCharsToPixels(int) + */ + public int convertHeightInCharsToPixels(int chars) { + return Dialog.convertHeightInCharsToPixels(fFontMetrics, chars); + } + + /** + * see org.eclipse.jface.dialogs.DialogPage#convertHorizontalDLUsToPixels(int) + */ + public int convertHorizontalDLUsToPixels(int dlus) { + return Dialog.convertHorizontalDLUsToPixels(fFontMetrics, dlus); + } + + /** + * see org.eclipse.jface.dialogs.DialogPage#convertVerticalDLUsToPixels(int) + */ + public int convertVerticalDLUsToPixels(int dlus) { + return Dialog.convertVerticalDLUsToPixels(fFontMetrics, dlus); + } + + /** + * see org.eclipse.jface.dialogs.DialogPage#convertWidthInCharsToPixels(int) + */ + public int convertWidthInCharsToPixels(int chars) { + return Dialog.convertWidthInCharsToPixels(fFontMetrics, chars); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/UiUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/UiUtils.java new file mode 100644 index 0000000..126efba --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/UiUtils.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.ui; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Shell; + +/** + * Utilities for UI. + * + * @author scheglov_ke + */ +public class UiUtils { + //////////////////////////////////////////////////////////////////////////// + // + // Message dialogs + // + //////////////////////////////////////////////////////////////////////////// + /** + * Opens standard warning dialog. + */ + public static void openWarning(Shell parent, String title, String message) { + MessageDialog dialog = + new MessageDialog(parent, + title, + null, + message, + MessageDialog.WARNING, + new String[]{IDialogConstants.OK_LABEL}, + 0); + dialog.open(); + } +} \ No newline at end of file diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/ResizableDialog.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/ResizableDialog.java new file mode 100644 index 0000000..307c2f6 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/ResizableDialog.java @@ -0,0 +1,221 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.ui.dialogs; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.plugin.AbstractUIPlugin; + +/** + * {@link Dialog} that remembers location/size between usage sessions. + * + * @author scheglov_ke + * @coverage core.ui + */ +public abstract class ResizableDialog extends Dialog { + /** + * Key for accessing {@link Dialog} from its {@link Shell}. + */ + public static final String KEY_DIALOG = "KEY_DIALOG"; + //////////////////////////////////////////////////////////////////////////// + // + // Internal constants + // + //////////////////////////////////////////////////////////////////////////// + private static final String X = "x"; + private static final String Y = "y"; + private static final String WIDTH = "width"; + private static final String HEIGHT = "height"; + //////////////////////////////////////////////////////////////////////////// + // + // Instance fields + // + //////////////////////////////////////////////////////////////////////////// + private final AbstractUIPlugin m_plugin; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public ResizableDialog(Shell parentShell, AbstractUIPlugin plugin) { + super(parentShell); + m_plugin = plugin; + setShellStyle(getShellStyle() | SWT.RESIZE | SWT.MAX); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Size + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected Point getInitialSize() { + // track the current dialog bounds + installDialogBoundsTracker(); + // answer the size from the previous incarnation + Point defaultSize = getDefaultSize(); + if ((getShellStyle() & SWT.RESIZE) != 0) { + Rectangle oldBounds = loadBounds(); + if (oldBounds != null) { + Rectangle displayBounds = getShell().getDisplay().getBounds(); + int width = Math.min(displayBounds.width, Math.max(oldBounds.width, defaultSize.x)); + int height = Math.min(displayBounds.height, Math.max(oldBounds.height, defaultSize.y)); + return new Point(width, height); + } + } + // use default size + return defaultSize; + } + + /** + * @return the default size of dialog. + */ + protected Point getDefaultSize() { + return super.getInitialSize(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Location + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected Point getInitialLocation(Point initialSize) { + Rectangle windowBounds; + { + Shell windowShell = m_plugin.getWorkbench().getActiveWorkbenchWindow().getShell(); + windowBounds = windowShell.getBounds(); + } + // answer the location from the previous incarnation + Rectangle bounds = loadBounds(); + if (bounds != null) { + int x = bounds.x; + int y = bounds.y; + int maxX = windowBounds.x + windowBounds.width - initialSize.x; + int maxY = windowBounds.y + windowBounds.height - initialSize.y; + if (x > maxX) { + x = maxX; + } + if (y > maxY) { + y = maxY; + } + if (x < windowBounds.x) { + x = windowBounds.x; + } + if (y < windowBounds.y) { + y = windowBounds.y; + } + return new Point(x, y); + } + // default location - centered on workbench window + int x = windowBounds.x + (windowBounds.width - initialSize.x) / 2; + int y = windowBounds.y + (windowBounds.height - initialSize.y) / 2; + return new Point(x, y); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Bounds + // + //////////////////////////////////////////////////////////////////////////// + /** + * Loads bounds from {@link IDialogSettings}. + */ + private Rectangle loadBounds() { + IDialogSettings settings = getDialogSettings(); + try { + return new Rectangle(settings.getInt(X), + settings.getInt(Y), + settings.getInt(WIDTH), + settings.getInt(HEIGHT)); + } catch (NumberFormatException e) { + return null; + } + } + + /** + * Saves bounds to {@link IDialogSettings}. + */ + private void saveBounds(Rectangle bounds) { + IDialogSettings settings = getDialogSettings(); + settings.put(X, bounds.x); + settings.put(Y, bounds.y); + settings.put(WIDTH, bounds.width); + settings.put(HEIGHT, bounds.height); + } + + /** + * @return the {@link IDialogSettings} for this dialog with this type. + */ + protected IDialogSettings getDialogSettings() { + IDialogSettings settings = m_plugin.getDialogSettings(); + String sectionName = getDialogSettingsSectionName(); + if (settings.getSection(sectionName) == null) { + return settings.addNewSection(sectionName); + } + return settings.getSection(sectionName); + } + + /** + * @return the name of section for dialog specific bounds. By default uses name of {@link Class}, + * but if same dialog is used for displaying different content, then may be overridden. + */ + protected String getDialogSettingsSectionName() { + return getClass().getName(); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Size tracking + // + //////////////////////////////////////////////////////////////////////////// + protected Rectangle cachedBounds; + + private void installDialogBoundsTracker() { + getShell().addControlListener(new ControlListener() { + public void controlMoved(ControlEvent e) { + cachedBounds = getShell().getBounds(); + } + + public void controlResized(ControlEvent e) { + cachedBounds = getShell().getBounds(); + } + }); + } + + @Override + public boolean close() { + boolean shellMaximized = getShell().getMaximized(); + boolean closed = super.close(); + if (closed && !shellMaximized && cachedBounds != null) { + saveBounds(cachedBounds); + } + return closed; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Shell + // + //////////////////////////////////////////////////////////////////////////// + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setData(KEY_DIALOG, this); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/StringsDialog.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/StringsDialog.java new file mode 100644 index 0000000..745bcc3 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/StringsDialog.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.ui.dialogs; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; + +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils; +import org.eclipse.wb.internal.core.utils.execution.RunnableObjectEx; + +import java.io.BufferedReader; +import java.io.StringReader; +import java.util.List; + +/** + * The dialog for editing array of {@link String}'s. + * + * @author scheglov_ke + * @coverage core.ui + */ +public class StringsDialog extends TextDialog { + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public StringsDialog(Shell parentShell, + AbstractUIPlugin plugin, + String titleText, + String headerText, + String footerText) { + super(parentShell, plugin, titleText, headerText, footerText); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Items + // + //////////////////////////////////////////////////////////////////////////// + /** + * Sets the items to edit. + */ + public void setItems(String[] items) { + setText(Joiner.on('\n').join(items)); + } + + /** + * @return the edited items. + */ + public String[] getItems() { + return ExecutionUtils.runObjectLog(new RunnableObjectEx() { + @Override + public String[] runObject() throws Exception { + List strings = Lists.newArrayList(); + BufferedReader br = new BufferedReader(new StringReader(getText())); + while (true) { + String s = br.readLine(); + if (s == null) { + break; + } + strings.add(s); + } + return strings.toArray(new String[strings.size()]); + } + }, new String[0]); + } +} diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/TextDialog.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/TextDialog.java new file mode 100644 index 0000000..320fa32 --- /dev/null +++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/TextDialog.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * 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: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.core.utils.ui.dialogs; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.eclipse.wb.internal.core.utils.ui.GridDataFactory; +import org.eclipse.wb.internal.core.utils.ui.GridLayoutFactory; + +/** + * The dialog for editing multiline text. + * + * @author scheglov_ke + * @coverage core.ui + */ +public class TextDialog extends ResizableDialog { + private final String m_titleText; + private final String m_headerText; + private final String m_footerText; + + //////////////////////////////////////////////////////////////////////////// + // + // Constructor + // + //////////////////////////////////////////////////////////////////////////// + public TextDialog(Shell parentShell, + AbstractUIPlugin plugin, + String titleText, + String headerText, + String footerText) { + super(parentShell, plugin); + m_titleText = titleText; + m_headerText = headerText; + m_footerText = footerText; + } + + //////////////////////////////////////////////////////////////////////////// + // + // Text + // + //////////////////////////////////////////////////////////////////////////// + private String m_text; + + /** + * Sets the text to edit. + */ + public final void setText(String text) { + m_text = text; + } + + /** + * @return the edited text. + */ + public final String getText() { + return m_text; + } + + //////////////////////////////////////////////////////////////////////////// + // + // GUI + // + //////////////////////////////////////////////////////////////////////////// + protected Text m_textWidget; + + @Override + protected Control createDialogArea(Composite parent) { + Composite area = (Composite) super.createDialogArea(parent); + GridLayoutFactory.create(area); + // header + new Label(area, SWT.NONE).setText(m_headerText); + // Text widget + { + m_textWidget = new Text(area, SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + GridDataFactory.create(m_textWidget).grab().fill().hintVC(10); + m_textWidget.setText(m_text); + // handle Ctrl+Enter as OK + m_textWidget.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.stateMask == SWT.CTRL && e.keyCode == SWT.CR) { + okPressed(); + } + } + }); + } + // footer + new Label(area, SWT.NONE).setText(m_footerText); + // + return area; + } + + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText(m_titleText); + } + + @Override + protected void okPressed() { + m_text = m_textWidget.getText(); + super.okPressed(); + } +} -- cgit v1.2.3