diff options
Diffstat (limited to 'common/plugins/org.eclipse.jpt.common.ui')
95 files changed, 21108 insertions, 0 deletions
diff --git a/common/plugins/org.eclipse.jpt.common.ui/.classpath b/common/plugins/org.eclipse.jpt.common.ui/.classpath new file mode 100644 index 0000000000..bd4371771c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/.classpath @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"> + <accessrules> + <accessrule kind="accessible" pattern="org/eclipse/wst/**"/> + <accessrule kind="accessible" pattern="org/eclipse/jst/**"/> + </accessrules> + </classpathentry> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="property_files"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/common/plugins/org.eclipse.jpt.common.ui/.project b/common/plugins/org.eclipse.jpt.common.ui/.project new file mode 100644 index 0000000000..7fde966914 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.jpt.common.ui</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/common/plugins/org.eclipse.jpt.common.ui/.settings/org.eclipse.jdt.core.prefs b/common/plugins/org.eclipse.jpt.common.ui/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..6e2f42326d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Mon Jan 24 09:48:38 EST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/common/plugins/org.eclipse.jpt.common.ui/META-INF/MANIFEST.MF b/common/plugins/org.eclipse.jpt.common.ui/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..1dcfdd5939 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/META-INF/MANIFEST.MF @@ -0,0 +1,36 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-Vendor: %providerName +Bundle-SymbolicName: org.eclipse.jpt.common.ui;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.eclipse.jpt.common.ui.JptCommonUiPlugin +Bundle-ActivationPolicy: lazy +Bundle-ClassPath: . +Bundle-Localization: plugin +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Require-Bundle: org.eclipse.core.resources;bundle-version="[3.4.0,4.0.0)", + org.eclipse.draw2d;bundle-version="[3.4.0,4.0.0)", + org.eclipse.jdt.core;bundle-version="[3.4.0,4.0.0)", + org.eclipse.jdt.ui;bundle-version="[3.4.0,4.0.0)", + org.eclipse.jface;bundle-version="[3.7.0,4.0.0)", + org.eclipse.jface.text;bundle-version="[3.4.0,4.0.0)", + org.eclipse.jpt.common.core;bundle-version="[1.0.0,2.0.0)", + org.eclipse.jpt.utility;bundle-version="[1.2.0,2.0.0)", + org.eclipse.jst.common.project.facet.core;bundle-version="[1.3.100,2.0.0)", + org.eclipse.jst.common.project.facet.ui;bundle-version="[1.3.100,2.0.0)", + org.eclipse.ui.navigator;bundle-version="[3.3.100,4.0.0)", + org.eclipse.ui.views.properties.tabbed;bundle-version="[3.4.0,4.0.0)", + org.eclipse.wst.common.project.facet.ui;bundle-version="[1.3.0,2.0.0)" +Export-Package: org.eclipse.jpt.common.ui, + org.eclipse.jpt.common.ui.internal;x-friends:="org.eclipse.jpt.jaxb.ui,org.eclipse.jpt.ui", + org.eclipse.jpt.common.ui.internal.jface;x-friends:="org.eclipse.jpt.jaxb.ui,org.eclipse.jpt.ui", + org.eclipse.jpt.common.ui.internal.listeners;x-friends:="org.eclipse.jpt.jaxb.ui,org.eclipse.jpt.ui", + org.eclipse.jpt.common.ui.internal.properties, + org.eclipse.jpt.common.ui.internal.swt;x-friends:="org.eclipse.jpt.jaxb.ui,org.eclipse.jpt.ui", + org.eclipse.jpt.common.ui.internal.util;x-friends:="org.eclipse.jpt.jaxb.ui,org.eclipse.jpt.ui", + org.eclipse.jpt.common.ui.internal.utility;x-friends:="org.eclipse.jpt.jaxb.ui,org.eclipse.jpt.ui", + org.eclipse.jpt.common.ui.internal.utility.swt;x-friends:="org.eclipse.jpt.jaxb.ui,org.eclipse.jpt.ui", + org.eclipse.jpt.common.ui.internal.widgets;x-friends:="org.eclipse.jpt.jaxb.ui,org.eclipse.jpt.ui", + org.eclipse.jpt.common.ui.jface +Import-Package: com.ibm.icu.text;version="4.0.1" diff --git a/common/plugins/org.eclipse.jpt.common.ui/about.html b/common/plugins/org.eclipse.jpt.common.ui/about.html new file mode 100644 index 0000000000..be534ba44f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/about.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"> +<HTML> + +<head> +<title>About</title> +<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1"> +</head> + +<BODY lang="EN-US"> + +<H3>About This Content</H3> + +<P>May 02, 2008</P> + +<H3>License</H3> + +<P>The Eclipse Foundation makes available all content in this plug-in +("Content"). Unless otherwise indicated below, the Content is provided to you +under the terms and conditions of the Eclipse Public License Version 1.0 +("EPL"). A copy of the EPL is available at +<A href="http://www.eclipse.org/org/documents/epl-v10.php">http://www.eclipse.org/org/documents/epl-v10.php</A>. +For purposes of the EPL, "Program" will mean the Content.</P> + +<P>If you did not receive this Content directly from the Eclipse Foundation, the +Content is being redistributed by another party ("Redistributor") and different +terms and conditions may apply to your use of any object code in the Content. +Check the Redistributor's license that was provided with the Content. If no such +license exists, contact the Redistributor. Unless otherwise indicated below, the +terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at +<A href="http://www.eclipse.org/">http://www.eclipse.org/</A>.</P> + +</BODY> +</HTML> diff --git a/common/plugins/org.eclipse.jpt.common.ui/build.properties b/common/plugins/org.eclipse.jpt.common.ui/build.properties new file mode 100644 index 0000000000..87792f2ef5 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/build.properties @@ -0,0 +1,19 @@ +################################################################################ +# Copyright (c) 2011 Oracle. 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: +# Oracle - initial API and implementation +################################################################################ +javacSource = 1.5 +javacTarget = 1.5 +source.. = src/,\ + property_files/ +output.. = bin/ +bin.includes = .,\ + META-INF/,\ + about.html,\ + plugin.properties +jars.compile.order = . diff --git a/common/plugins/org.eclipse.jpt.common.ui/plugin.properties b/common/plugins/org.eclipse.jpt.common.ui/plugin.properties new file mode 100644 index 0000000000..4caf90e773 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/plugin.properties @@ -0,0 +1,23 @@ +############################################################################### +# Copyright (c) 2011 Oracle. 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: +# Oracle - initial API and implementation +############################################################################### + +# ==================================================================== +# To code developer: +# Do NOT change the properties between this line and the +# "%%% END OF TRANSLATED PROPERTIES %%%" line. +# Make a new property name, append to the end of the file and change +# the code to use the new property. +# ==================================================================== + +# ==================================================================== +# %%% END OF TRANSLATED PROPERTIES %%% +# ==================================================================== +pluginName= Dali Java Persistence Tools - Common UI +providerName=Eclipse Web Tools Platform diff --git a/common/plugins/org.eclipse.jpt.common.ui/property_files/jpt_common_ui.properties b/common/plugins/org.eclipse.jpt.common.ui/property_files/jpt_common_ui.properties new file mode 100644 index 0000000000..aee4769cae --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/property_files/jpt_common_ui.properties @@ -0,0 +1,28 @@ +################################################################################ +# Copyright (c) 2011 Oracle. 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: +# Oracle - initial API and implementation +################################################################################ + +Boolean_True=True +Boolean_False=False + +NoneSelected=<None> +DefaultEmpty=Default +DefaultWithOneParam=Default ({0}) + +AddRemovePane_AddButtonText=Add... +AddRemovePane_RemoveButtonText=Remove +ChooserPane_browseButton=Browse... +ClassChooserPane_dialogMessage=&Enter type name prefix or pattern (*, ?, or camel case): +ClassChooserPane_dialogTitle=Class Selection +PackageChooserPane_dialogTitle=Package Selection +PackageChooserPane_dialogMessage=&Enter package name prefix or pattern (* = any string, ? = any character): +EnumComboViewer_default=Default () +EnumComboViewer_defaultWithDefault=Default ({0}) +NewNameStateObject_nameMustBeSpecified=A name must be specified. +NewNameStateObject_nameAlreadyExists=A query with this name already exists. diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/JptCommonUiPlugin.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/JptCommonUiPlugin.java new file mode 100644 index 0000000000..c249775143 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/JptCommonUiPlugin.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2011 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.ui.plugin.AbstractUIPlugin; + +public class JptCommonUiPlugin + extends AbstractUIPlugin +{ + + // ********** constants ********** + + /** + * The plug-in identifier of JPT Common UI support (value {@value}). + */ + public static final String PLUGIN_ID = "org.eclipse.jpt.common.ui"; //$NON-NLS-1$ + public static final String PLUGIN_ID_ = PLUGIN_ID + '.'; + + // ********** singleton ********** + + private static JptCommonUiPlugin INSTANCE; + + /** + * Returns the singleton JPT UI plug-in. + */ + public static JptCommonUiPlugin instance() { + return INSTANCE; + } + + + // ********** logging ********** + + public static void log(IStatus status) { + INSTANCE.getLog().log(status); + } + + public static void log(String msg) { + log(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, msg, null)); + } + + public static void log(Throwable throwable) { + log(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, throwable.getLocalizedMessage(), throwable)); + } + + + // ********** construction ********** + + public JptCommonUiPlugin() { + super(); + if (INSTANCE != null) { + throw new IllegalStateException(); + } + INSTANCE = this; + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/WidgetFactory.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/WidgetFactory.java new file mode 100644 index 0000000000..4a7154aeee --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/WidgetFactory.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui; + +import org.eclipse.swt.custom.CCombo; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.DateTime; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.List; +import org.eclipse.swt.widgets.Spinner; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.Hyperlink; +import org.eclipse.ui.forms.widgets.Section; + +/** + * A widget factory is responsible for creating an SWT widget based on the right + * style. Some style shows the widgets differently, for instance, the flat style + * shows the widgets with less borders. + * <p> + * Provisional API: This interface is part of an interim API that is still under + * development and expected to change significantly before reaching stability. + * It is available at this early stage to solicit feedback from pioneering + * adopters on the understanding that any code that uses this API will almost + * certainly be broken (repeatedly) as the API evolves. + * + * @version 2.0 + * @since 2.0 + */ +public interface WidgetFactory { + + /** + * Creates a new regular button. + * + * @param parent The parent container + * @param text The button's text + * @return A new <code>Button</code> + */ + Button createButton(Composite parent, String text); + + /** + * Creates a new non-editable custom <code>Combo</code>. + * + * @deprecated + * @param parent The parent container + * @return A new <code>CCombo</code> + */ + @Deprecated + CCombo createCCombo(Composite parent); + + /** + * Creates a new check box button. + * + * @param parent The parent container + * @param text The button's text + * @return A new <code>Button</code> + */ + Button createCheckBox(Composite parent, String text); + + /** + * Creates a new non-editable <code>Combo</code>. + * + * @param parent The parent container + * @return A new <code>Combo</code> + */ + Combo createCombo(Composite parent); + + /** + * Creates a new container. + * + * @param parent The parent container + * @return A new <code>Composite</code> + */ + Composite createComposite(Composite parent); + + /** + * Creates a new DateTime. + * + * @param container The parent container + * @param style The style is to tell the type of widget + * (<code>SWT.DATE</code> or <code>SWT.TIME</code> or <code>SWT.CALENDAR</code>) + * @return A new <code>DateTime</code> + */ + DateTime createDateTime(Composite parent, int style); + + /** + * Creates a new editable custom <code>CCombo</code>. + * + * @deprecated + * @param parent The parent container + * @return A new <code>CCombo</code> + */ + @Deprecated + CCombo createEditableCCombo(Composite parent); + + /** + * Creates a new editable <code>Combo</code>. + * + * @param parent The parent container + * @return A new <code>Combo</code> + */ + Combo createEditableCombo(Composite parent); + + /** + * Creates a new titled pane (group box). + * + * @param parent The parent container + * @param title The group pane's title + * @return A new <code>Group</code> + */ + Group createGroup(Composite parent, String title); + + /** + * Creates a new label that is shown as a hyperlink. + * + * @param parent The parent container + * @param text The label's text + * @return A new <code>Hyperlink</code> + */ + Hyperlink createHyperlink(Composite parent, String text); + + /** + * Creates a new label. + * + * @param container The parent container + * @param labelText The label's text + * @return A new <code>Label</code> + */ + Label createLabel(Composite container, String labelText); + + /** + * Creates a new list. + * + * @param container The parent container + * @param style The style is usually to tell what type of selection + * (<code>SWT.MULTI</code> or <code>SWT.SINGLE</code>) + * @return A new <code>Label</code> + */ + List createList(Composite container, int style); + + /** + * Creates a new label that can be wrapped on multiple lines. + * + * @param container The parent container + * @param labelText The label's text + * @return A new <code>FormText</code> + */ + FormText createMultiLineLabel(Composite container, String labelText); + + /** + * Creates a new editable text area. + * + * @param parent The parent container + * @param parent The number of lines the text area should display + * @return A new <code>Text</code> + */ + Text createMultiLineText(Composite parent); + + /** + * Creates a new editable text field that handles password. + * + * @param container The parent container + * @return A new <code>Text</code> + */ + Text createPasswordText(Composite container); + + /** + * Creates a new push button (toggle between selected and unselected). + * + * @param parent The parent container + * @param text The button's text + * @return A new <code>Button</code> + */ + Button createPushButton(Composite parent, String text); + + /** + * Creates a new radio button. + * + * @param parent The parent container + * @param text The button's text + * @return A new <code>Button</code> + */ + Button createRadioButton(Composite parent, String text); + + /** + * Creates a new section, which is a collapsable pane with a title bar. + * + * @param parent The parent container + * @param style The style of the title bar, which can be + * <code>ExpandableComposite.TWISTIE</code> and + * <code>ExpandableComposite.TITLE_BAR</code> + * @return A new <code>Section</code> + */ + Section createSection(Composite parent, int style); + + /** + * Creates a new spinner. + * + * @param parent The parent container + * @return A new <code>Spinner</code> + */ + Spinner createSpinner(Composite parent); + + /** + * Creates a new table. + * + * @param container The parent container + * @param style The style to apply to the table + * @return A new <code>Table</code> + */ + Table createTable(Composite parent, int style); + + /** + * Creates a new editable text field. + * + * @param container The parent container + * @return A new <code>Text</code> + */ + Text createText(Composite parent); + + /** + * Creates a new tri-state check box. + * + * @param parent The parent container + * @param text The button's text + * @return A new <code>Button</code> that has 3 selection states + */ + Button createTriStateCheckBox(Composite parent, String text); +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/JptCommonUiMessages.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/JptCommonUiMessages.java new file mode 100644 index 0000000000..31a1080af3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/JptCommonUiMessages.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2006, 2011 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal; + +import org.eclipse.osgi.util.NLS; + +/** + * Localized messages used by Dali UI. + * + * @version 3.0 + * @since 1.0 + */ +public class JptCommonUiMessages { + + + public static String Boolean_True; + public static String Boolean_False; + + public static String DefaultEmpty; + public static String DefaultWithOneParam; + public static String NoneSelected; + + public static String AddRemovePane_AddButtonText; + public static String AddRemovePane_RemoveButtonText; + + public static String ChooserPane_browseButton; + public static String ClassChooserPane_dialogMessage; + public static String ClassChooserPane_dialogTitle; + public static String PackageChooserPane_dialogMessage; + public static String PackageChooserPane_dialogTitle; + public static String EnumComboViewer_default; + public static String EnumComboViewer_defaultWithDefault; + public static String NewNameStateObject_nameAlreadyExists; + public static String NewNameStateObject_nameMustBeSpecified; + + private static final String BUNDLE_NAME = "jpt_common_ui"; //$NON-NLS-1$ + private static final Class<?> BUNDLE_CLASS = JptCommonUiMessages.class; + static { + NLS.initializeMessages(BUNDLE_NAME, BUNDLE_CLASS); + } + + private JptCommonUiMessages() { + throw new UnsupportedOperationException(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/Tracing.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/Tracing.java new file mode 100644 index 0000000000..7826015d18 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/Tracing.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal; + +import org.eclipse.core.runtime.Platform; +import org.eclipse.jpt.common.ui.JptCommonUiPlugin; + +/** + * This tracing class manages to convert the string value into boolean values or + * integer values that are associated with the tracing debug flags. Those flags + * are specified in the .options file. The supported keys are defined here as + * constants for quick reference. + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public final class Tracing +{ + /** + * A constant used to retrieve the value associated with "/debug". + */ + public static final String DEBUG = "/debug"; + + /** + * A constant used to retrieve the value associated with "/debug/ui/db". + */ + public static final String UI_DB = "/debug/ui/db"; + + /** + * A constant used to retrieve the value associated with "/debug/ui/detailsView". + */ + public static final String UI_DETAILS_VIEW = "/debug/ui/detailsView"; + + /** + * A constant used to retrieve the value associated with "/debug/ui/layout". + */ + public static final String UI_LAYOUT = "/debug/ui/layout"; + + /** + * A constant used to retrieve the value associated with "/unit-tests". + */ + public static final String UNIT_TESTS = "/unit-tests"; + + /** + * Can't instantiate this <code>Tracing</code> class. + */ + private Tracing() + { + super(); + throw new UnsupportedOperationException("Tracing cannot be instantiated"); + } + + /** + * Retrieves the debug value associated with the given flag. The default + * value is <code>false</code>. + * + * @param flag The flag to retrieve the debug value, which should be + * contained in the .options file, the flag should start with "/" + * @return <code>true</code> if the given flag is active; <code>false</code> + * otherwise + */ + public static boolean booleanDebugOption(String flag) + { + return booleanDebugOption(flag, false); + } + + /** + * Retrieves the debug value associated with the given flag. + * + * @param flag The flag to retrieve the debug value, which should be + * contained in the .options file, the flag should start with "/" + * @param defaultValue The default value if the value associated with the + * given flag could not be found + * @return <code>true</code> if the given flag is active; <code>false</code> + * otherwise + */ + public static boolean booleanDebugOption(String flag, boolean defaultValue) + { + String string = Platform.getDebugOption(JptCommonUiPlugin.PLUGIN_ID + flag); + return (string == null) ? defaultValue : Boolean.parseBoolean(string.trim()); + } + + /** + * Retrieves the debug value associated with the given flag. The default value + * is 0. + * + * @param flag The flag to retrieve the debug value, which should be + * contained in the .options file, the flag should start with "/" + * @return The value associated with the given flag, or the given default + * value + */ + public static int intDebugOption(String flag) + { + return intDebugOption(flag, 0); + } + + /** + * Retrieves the debug value associated with the given flag. + * + * @param flag The flag to retrieve the debug value, which should be + * contained in the .options file, the flag should start with "/" + * @param defaultValue The default value if the value associated with the + * given flag could not be found + * @return The value associated with the given flag, or the given default + * value + */ + public static int intDebugOption(String flag, int defaultValue) + { + String string = Platform.getDebugOption(JptCommonUiPlugin.PLUGIN_ID + flag); + return (string == null) ? defaultValue : Integer.parseInt(string); + } + + /** + * Logs the given messages, appends it with this plug-in id. + * + * @param message The message to be logged + */ + public static void log(String message) + { + System.out.print("[" + JptCommonUiPlugin.PLUGIN_ID + "] "); + System.out.println(message); + } + + /** + * Retrieves the debug value associated with the given flag. The default value + * is an empty string. + * + * @param flag The flag to retrieve the debug value, which should be + * contained in the .options file, the flag should start with "/" + * @return The value associated with the given flag, or the given default + * value + */ + public static String stringDebugOption(String flag) + { + return stringDebugOption(flag, ""); + } + + /** + * Retrieves the debug value associated with the given flag. + * + * @param flag The flag to retrieve the debug value, which should be + * contained in the .options file, the flag should start with "/" + * @param defaultValue The default value if the value associated with the + * given flag could not be found + * @return The value associated with the given flag, or the given default + * value + */ + public static String stringDebugOption(String flag, String defaultValue) + { + String string = Platform.getDebugOption(JptCommonUiPlugin.PLUGIN_ID + flag); + return (string != null) ? string : defaultValue; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/AbstractItemLabelProvider.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/AbstractItemLabelProvider.java new file mode 100644 index 0000000000..abcaa7bc48 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/AbstractItemLabelProvider.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.jface; + +import org.eclipse.jpt.common.ui.jface.DelegatingContentAndLabelProvider; +import org.eclipse.jpt.common.ui.jface.ItemLabelProvider; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.graphics.Image; + +/** + * Implementation of {@link ItemLabelProvider} that provides updating + * label information for a Model object. + * + * The typical subclass will override the following methods: + * #buildImageModel() + * return a {@link PropertyValueModel} that represents the image for the + * represented model object + * #buildTextModel() + * return a {@link PropertyValueModel} that represents the text for the + * represented model object. + * #buildDescriptionModel() + * return a {@link PropertyValueModel} that represents the description for + * the represented model object + * + * Other methods may be overridden, but take care to preserve the logic provided + * by this class. + */ +public abstract class AbstractItemLabelProvider implements ItemLabelProvider +{ + private DelegatingContentAndLabelProvider labelProvider; + + private Model model; + + private PropertyValueModel<Image> imageModel; + + private PropertyValueModel<String> textModel; + + private PropertyValueModel<String> descriptionModel; + + private PropertyChangeListener labelChangeListener; + + + protected AbstractItemLabelProvider( + Model model, DelegatingContentAndLabelProvider labelProvider) { + this.model = model; + this.labelProvider = labelProvider; + this.labelChangeListener = buildLabelChangeListener(); + } + + + /** + * Construct a listener to update the viewer (through the label provider) + * if the text or image changes + */ + protected PropertyChangeListener buildLabelChangeListener() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + labelProvider().updateLabel(model()); + } + }; + } + + /** + * Return the image value model + * (lazy and just-in-time initialized) + */ + protected synchronized PropertyValueModel<Image> imageModel() { + if (this.imageModel == null) { + this.imageModel = buildImageModel(); + engageImageModel(); + } + return this.imageModel; + } + + /** + * Construct an image model + */ + protected abstract PropertyValueModel<Image> buildImageModel(); + + /** + * Should only be overridden with a call to super.engageImageModel() before + * subclass logic + */ + protected void engageImageModel() { + this.imageModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.labelChangeListener); + } + + /** + * Should only be overridden with a call to super.disengageImageModel() after + * subclass logic + */ + protected void disengageImageModel() { + this.imageModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.labelChangeListener); + } + + /** + * Return the text value model + * (lazy and just-in-time initialized) + */ + protected synchronized PropertyValueModel<String> textModel() { + if (this.textModel == null) { + this.textModel = buildTextModel(); + engageTextModel(); + } + return this.textModel; + } + + /** + * Construct a text value model + */ + protected abstract PropertyValueModel<String> buildTextModel(); + + /** + * Should only be overridden with a call to super.engageTextModel() before + * subclass logic + */ + protected void engageTextModel() { + this.textModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.labelChangeListener); + } + + /** + * Should only be overridden with a call to super.disengageTextModel() after + * subclass logic + */ + protected void disengageTextModel() { + this.textModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.labelChangeListener); + } + + /** + * Return the description value model + * (lazy and just-in-time initialized) + */ + protected synchronized PropertyValueModel<String> descriptionModel() { + if (this.descriptionModel == null) { + this.descriptionModel = buildDescriptionModel(); + engageDescriptionModel(); + } + return this.descriptionModel; + } + + /** + * Construct a description value model + */ + protected abstract PropertyValueModel<String> buildDescriptionModel(); + + /** + * Should only be overridden with a call to super.engageDescriptionModel() before + * subclass logic + */ + protected void engageDescriptionModel() { + this.descriptionModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.labelChangeListener); + } + + /** + * Should only be overridden with a call to super.disengageDescriptionModel() after + * subclass logic + */ + protected void disengageDescriptionModel() { + this.descriptionModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.labelChangeListener); + } + + /** + * Return the model object represented by this item + */ + public Model model() { + return this.model; + } + + /** + * Return the label provider that delegates to this item + */ + public DelegatingContentAndLabelProvider labelProvider() { + return this.labelProvider; + } + + public Image getImage() { + return imageModel().getValue(); + } + + public String getText() { + return textModel().getValue(); + } + + public String getDescription() { + return descriptionModel().getValue(); + } + + public void dispose() { + disposeTextModel(); + disposeImageModel(); + disposeDescriptionModel(); + } + + protected synchronized void disposeTextModel() { + if (this.textModel != null) { + disengageTextModel(); + this.textModel = null; + } + } + + protected synchronized void disposeImageModel() { + if (this.imageModel != null) { + disengageImageModel(); + this.imageModel = null; + } + } + + protected synchronized void disposeDescriptionModel() { + if (this.descriptionModel != null) { + disengageDescriptionModel(); + this.descriptionModel = null; + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/AbstractTreeItemContentProvider.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/AbstractTreeItemContentProvider.java new file mode 100644 index 0000000000..295540dbb7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/AbstractTreeItemContentProvider.java @@ -0,0 +1,204 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.jface; + +import java.util.Iterator; + +import org.eclipse.jpt.common.ui.jface.TreeItemContentProvider; +import org.eclipse.jpt.utility.internal.ArrayTools; +import org.eclipse.jpt.utility.internal.model.value.ListCollectionValueModelAdapter; +import org.eclipse.jpt.utility.internal.model.value.NullCollectionValueModel; +import org.eclipse.jpt.utility.internal.model.value.PropertyListValueModelAdapter; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; + +/** + * Implementation of {@link TreeItemContentProvider} that provides updating + * children information for a Model object. + * + * The typical subclass will override the following methods: + * #getParent() + * the default behavior for this method is to return null. there is no + * property value model for this as this should not be changing for a given + * node. all such changes will be provided by the parent side of the relationship. + * #buildChildrenModel() + * return a {@link ListValueModel} that represents the children for the represented + * model object. #buildChildrenModel(CollectionValueModel) and + * #buildChildrenModel(PropertyValueModel) are provided if the children are more + * easily represented as a collection or as a property (single child) + * the default behavior is to return a {@link NullListValueModel} + * + * Other methods may be overridden, but take care to preserve the logic provided + * by this class. + */ +public abstract class AbstractTreeItemContentProvider<E> + implements TreeItemContentProvider +{ + private DelegatingTreeContentAndLabelProvider treeContentProvider; + + private Model model; + + private CollectionValueModel<E> childrenModel; + + private CollectionChangeListener childrenListener; + + + protected AbstractTreeItemContentProvider( + Model model, DelegatingTreeContentAndLabelProvider treeContentProvider) { + this.model = model; + this.treeContentProvider = treeContentProvider; + this.childrenListener = buildChildrenListener(); + } + + /** + * Construct a listener to refresh the tree (through the tree content provider) + * if the children change + */ + protected CollectionChangeListener buildChildrenListener() { + return new CollectionChangeListener() { + + public void itemsAdded(CollectionAddEvent event) { + getTreeContentProvider().updateContent(getModel()); + } + + public void itemsRemoved(CollectionRemoveEvent event) { + getTreeContentProvider().updateContent(getModel()); + for (Object item : event.getItems()) { + getTreeContentProvider().dispose(item); + } + } + + public void collectionChanged(CollectionChangeEvent event) { + getTreeContentProvider().updateContent(getModel()); + // in the case of a list changed event, we don't have + // access to the removed objects, so we can't dispose them. + // keep a watch on this to see if this becomes a problem. + } + + public void collectionCleared(CollectionClearEvent event) { + getTreeContentProvider().updateContent(getModel()); + // in the case of a list cleared event, we don't have + // access to the removed objects, so we can't dispose them. + // keep a watch on this to see if this becomes a problem. + } + }; + } + + /** + * Return the children model + * (lazy and just-in-time initialized) + */ + protected synchronized Iterator<E> childrenModel() { + if (this.childrenModel == null) { + this.childrenModel = buildChildrenModel(); + engageChildren(); + } + return this.childrenModel.iterator(); + } + + /** + * Construct a children model + */ + protected CollectionValueModel<E> buildChildrenModel() { + return new NullCollectionValueModel<E>(); + } + + /** + * Utility method that can be used if the children model is better represented + * as a collection. + * This wraps the children collection model and uses it internally as a list + * model. + */ + protected CollectionValueModel<E> buildChildrenModel(ListValueModel<E> lvm) { + return new ListCollectionValueModelAdapter<E>(lvm); + } + + /** + * Utility method that can be used if the children model is better represented + * as a single value property. + * This wraps the children (child) property model and uses it internally as a list + * model. + */ + protected ListValueModel<E> buildChildrenModel(PropertyValueModel<E> lvm) { + return new PropertyListValueModelAdapter<E>(lvm); + } + + /** + * Return the model object represented by this node + */ + public Model getModel() { + return this.model; + } + + /** + * Return the tree content provider that delegates to this node + */ + public DelegatingTreeContentAndLabelProvider getTreeContentProvider() { + return this.treeContentProvider; + } + + public Object getParent() { + return null; + } + + public Object[] getElements() { + return getChildren(); + } + + public Object[] getChildren() { + return ArrayTools.array(this.childrenModel()); + } + + /** + * Override with potentially more efficient logic + */ + public boolean hasChildren() { + return this.childrenModel().hasNext(); + } + + /** + * Should only be overridden with a call to super.dispose() + */ + public void dispose() { + for (Object child : getChildren()) { + getTreeContentProvider().dispose(child); + } + disposeChildrenModel(); + } + + /** + * Should only be overridden with a call to super.engageChildren() before + * subclass logic + */ + protected void engageChildren() { + this.childrenModel.addCollectionChangeListener(CollectionValueModel.VALUES, this.childrenListener); + } + + protected synchronized void disposeChildrenModel() { + if (this.childrenModel != null) { + this.disengageChildrenModel(); + this.childrenModel = null; + } + } + /** + * Should only be overridden with a call to super.disengageChildren() after + * subclass logic + */ + protected void disengageChildrenModel() { + this.childrenModel.removeCollectionChangeListener(CollectionValueModel.VALUES, this.childrenListener); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/ArchiveFileViewerFilter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/ArchiveFileViewerFilter.java new file mode 100644 index 0000000000..bbff5516fc --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/ArchiveFileViewerFilter.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. + * 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: + * Oracle - initial API and implementation + *******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.jface; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.jpt.common.ui.JptCommonUiPlugin; + +/** + * This filter will deny showing any file that are not JAR files or folders + * that don't contain any JAR files in its sub-hierarchy. + */ +public class ArchiveFileViewerFilter + extends ViewerFilter +{ + private static final String[] archiveExtensions= { "jar", "zip" }; //$NON-NLS-1$ //$NON-NLS-2$ + + + public ArchiveFileViewerFilter() { + super(); + } + + + @Override + public boolean select( + Viewer viewer, Object parentElement, Object element) { + if (element instanceof IFile) { + return isArchivePath(((IFile)element).getFullPath()); + } + else if (element instanceof IFolder) { + IFolder folder = (IFolder) element; + try { + for (IResource each : folder.members()) { + if (select(viewer, folder, each)) { + return true; + } + } + } + catch (CoreException ce) { + // just skip this one, then + JptCommonUiPlugin.log(ce); + } + } + return false; + } + + public static boolean isArchivePath(IPath path) { + String ext= path.getFileExtension(); + if (ext != null && ext.length() != 0) { + for (int i= 0; i < archiveExtensions.length; i++) { + if (ext.equalsIgnoreCase(archiveExtensions[i])) { + return true; + } + } + } + return false; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/DelegatingTreeContentAndLabelProvider.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/DelegatingTreeContentAndLabelProvider.java new file mode 100644 index 0000000000..d5df216b6e --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/DelegatingTreeContentAndLabelProvider.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2007, 2011 Oracle. + * 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: + * Oracle - initial API and implementation + *******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.jface; + +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jpt.common.ui.jface.DelegatingContentAndLabelProvider; +import org.eclipse.jpt.common.ui.jface.ItemLabelProviderFactory; +import org.eclipse.jpt.common.ui.jface.TreeItemContentProvider; +import org.eclipse.jpt.common.ui.jface.TreeItemContentProviderFactory; + +/** + * Extension of {@link DelegatingContentAndLabelProvider} that provides an extension + * to provide tree content + */ +public class DelegatingTreeContentAndLabelProvider + extends DelegatingContentAndLabelProvider + implements ITreeContentProvider +{ + public DelegatingTreeContentAndLabelProvider( + TreeItemContentProviderFactory treeItemContentProviderFactory) { + super(treeItemContentProviderFactory); + } + + public DelegatingTreeContentAndLabelProvider( + TreeItemContentProviderFactory treeItemContentProviderFactory, + ItemLabelProviderFactory itemLabelProviderFactory) { + super(treeItemContentProviderFactory, itemLabelProviderFactory); + } + + + @Override + protected TreeItemContentProvider itemContentProvider(Object item) { + return (TreeItemContentProvider) super.itemContentProvider(item); + } + + public Object[] getChildren(Object parentElement) { + TreeItemContentProvider provider = itemContentProvider(parentElement); + return (provider == null) ? new Object[0] : provider.getChildren(); + } + + public Object getParent(Object element) { + TreeItemContentProvider provider = itemContentProvider(element); + return (provider == null) ? null : provider.getParent(); + } + + public boolean hasChildren(Object element) { + TreeItemContentProvider provider = itemContentProvider(element); + return (provider == null) ? false : provider.hasChildren(); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/ImageImageDescriptor.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/ImageImageDescriptor.java new file mode 100644 index 0000000000..e2079e0227 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/ImageImageDescriptor.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.jpt.common.ui.internal.jface; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; + +/** + * Image descriptor for an image. + */ +public class ImageImageDescriptor extends ImageDescriptor +{ + + private Image fImage; + + /** + * Constructor for ImagImageDescriptor. + */ + public ImageImageDescriptor(Image image) { + super(); + this.fImage = image; + } + + @Override + public boolean equals(Object obj) { + return (obj != null) && getClass().equals(obj.getClass()) && this.fImage.equals(((ImageImageDescriptor) obj).fImage); + } + + @Override + public ImageData getImageData() { + return this.fImage.getImageData(); + } + + @Override + public int hashCode() { + return this.fImage.hashCode(); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/NullLabelProvider.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/NullLabelProvider.java new file mode 100644 index 0000000000..1380b3dac6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/NullLabelProvider.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.jface; + +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.swt.graphics.Image; + +/** + * Null implementation of the ILabelProvider interface. + * Implemented as a singleton. + */ +public final class NullLabelProvider + implements ILabelProvider +{ + public static final NullLabelProvider INSTANCE = new NullLabelProvider(); + + public static ILabelProvider instance() { + return INSTANCE; + } + + /** + * Ensure a single instance. + */ + private NullLabelProvider() { + super(); + } + + public Image getImage(Object element) { + return null; + } + + public String getText(Object element) { + return null; + } + + public void addListener(ILabelProviderListener listener) { + // do nothing + } + + public void dispose() { + // do nothing + } + + public boolean isLabelProperty(Object element, String property) { + return false; + } + + public void removeListener(ILabelProviderListener listener) { + // do nothing + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/NullTreeContentProvider.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/NullTreeContentProvider.java new file mode 100644 index 0000000000..f050ffa226 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/NullTreeContentProvider.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.jface; + +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * Null implementation of the ILabelProvider interface. + * Implemented as a singleton. + */ +public final class NullTreeContentProvider + implements ITreeContentProvider +{ + private static final Object[] EMPTY_ARRAY = new Object[0]; + public static final NullTreeContentProvider INSTANCE = new NullTreeContentProvider(); + + public static ITreeContentProvider instance() { + return INSTANCE; + } + + /** + * Ensure a single instance. + */ + private NullTreeContentProvider() { + super(); + } + + public Object[] getChildren(Object parentElement) { + return EMPTY_ARRAY; + } + + public Object getParent(Object element) { + return null; + } + + public boolean hasChildren(Object element) { + return false; + } + + public Object[] getElements(Object inputElement) { + return EMPTY_ARRAY; + } + + public void dispose() { + // do nothing + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // do nothing + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/StructuredContentProviderAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/StructuredContentProviderAdapter.java new file mode 100644 index 0000000000..dfd2879520 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/StructuredContentProviderAdapter.java @@ -0,0 +1,265 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.jface; + +import org.eclipse.jface.viewers.AbstractListViewer; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jpt.common.ui.internal.listeners.SWTListChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.ArrayTools; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.internal.model.value.CollectionListValueModelAdapter; +import org.eclipse.jpt.utility.model.event.ListAddEvent; +import org.eclipse.jpt.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.utility.model.event.ListClearEvent; +import org.eclipse.jpt.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.utility.model.value.ListValueModel; + +/** + * This adapter can be used to keep an AbstractListViewer + * (e.g. a ListViewer or ComboViewer) in synch with a ListValueModel + * (or a CollectionValueModel). + */ +public class StructuredContentProviderAdapter + implements IStructuredContentProvider +{ + /** The underlying model list. */ + protected ListValueModel listHolder; + + /** The list viewer we keep in synch with the model list. */ + protected final AbstractListViewer listViewer; + + /** A listener that allows us to forward changes made to the underlying model list. */ + protected final ListChangeListener listChangeListener; + + + // ********** static ********** + + /** + * Adapt the specified list viewer to the specified list holder so they + * stay in synch. + */ + public static StructuredContentProviderAdapter adapt(AbstractListViewer listViewer, ListValueModel listHolder) { + // we need only construct the adapter and it will hook up to the list viewer etc. + return new StructuredContentProviderAdapter(listViewer, listHolder); + } + + /** + * Adapt the specified list viewer to the specified list holder so they + * stay in synch. + */ + public static StructuredContentProviderAdapter adapt(AbstractListViewer listViewer, CollectionValueModel collectionHolder) { + // we need only construct the adapter and it will hook up to the list viewer etc. + return new StructuredContentProviderAdapter(listViewer, collectionHolder); + } + + + // ********** constructors ********** + + /** + * Constructor. + */ + protected StructuredContentProviderAdapter(AbstractListViewer listViewer, ListValueModel listHolder) { + super(); + this.listChangeListener = this.buildListChangeListener(); + this.listViewer = listViewer; + this.listViewer.setContentProvider(this); + // the list viewer will call back to #inputChanged(Viewer, Object, Object) + this.listViewer.setInput(listHolder); + } + + /** + * Constructor. + */ + protected StructuredContentProviderAdapter(AbstractListViewer listViewer, CollectionValueModel collectionHolder) { + this(listViewer, new CollectionListValueModelAdapter(collectionHolder)); + } + + + // ********** initialization ********** + + protected ListChangeListener buildListChangeListener() { + return new SWTListChangeListenerWrapper(this.buildListChangeListener_()); + } + + protected ListChangeListener buildListChangeListener_() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent e) { + StructuredContentProviderAdapter.this.itemsAdded(e); + } + public void itemsRemoved(ListRemoveEvent e) { + StructuredContentProviderAdapter.this.itemsRemoved(e); + } + public void itemsReplaced(ListReplaceEvent e) { + StructuredContentProviderAdapter.this.itemsReplaced(e); + } + public void itemsMoved(ListMoveEvent e) { + StructuredContentProviderAdapter.this.itemsMoved(e); + } + public void listCleared(ListClearEvent e) { + StructuredContentProviderAdapter.this.listCleared(); + } + public void listChanged(ListChangeEvent e) { + StructuredContentProviderAdapter.this.listChanged(); + } + @Override + public String toString() { + return "list listener"; + } + }; + } + + + // ********** IStructuredContentProvider implementation ********** + + public Object[] getElements(Object inputElement) { + if (inputElement != this.listHolder) { + throw new IllegalArgumentException("invalid input element: " + inputElement); + } + return this.listHolder.toArray(); + } + + /** + * This is called by the list viewer, so don't update the list viewer here. + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (viewer != this.listViewer) { + throw new IllegalArgumentException("invalid viewer: " + viewer); + } + if (oldInput != this.listHolder) { + throw new IllegalArgumentException("invalid old input: " + oldInput); + } + this.modelChanged((ListValueModel) oldInput, (ListValueModel) newInput); + } + + public void dispose() { + // do nothing - listeners should've already been removed in #inputChanged(Viewer, Object, Object) + } + + + // ********** internal methods ********** + + protected void modelChanged(ListValueModel oldModel, ListValueModel newModel) { + if (oldModel != null) { + this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + this.listHolder = newModel; + if (newModel != null) { + this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + } + + + // ********** public API ********** + + /** + * Return the underlying list model. + */ + public ListValueModel model() { + return this.listHolder; + } + + /** + * Set the underlying list model. + */ + public void setModel(ListValueModel listHolder) { + // the list viewer will call back to #inputChanged(Viewer, Object, Object) + this.listViewer.setInput(listHolder); + } + + /** + * Set the underlying collection model. + */ + public void setModel(CollectionValueModel collectionHolder) { + this.setModel(new CollectionListValueModelAdapter(collectionHolder)); + } + + + // ********** list change support ********** + + /** + * Items were added to the underlying model list. + * Synchronize the list viewer. + */ + protected void itemsAdded(ListAddEvent e) { + int i = e.getIndex(); + for (Object item : e.getItems()) { + this.listViewer.insert(item, i++); + } + } + + /** + * Items were removed from the underlying model list. + * Synchronize the list viewer. + */ + protected void itemsRemoved(ListRemoveEvent e) { + this.listViewer.remove(ArrayTools.array(e.getItems(), e.getItemsSize())); + } + + /** + * Items were replaced in the underlying model list. + * Synchronize the list viewer. + */ + protected void itemsReplaced(ListReplaceEvent e) { + this.listViewer.remove(ArrayTools.array(e.getOldItems(), e.getItemsSize())); + int i = e.getIndex(); + for (Object item : e.getNewItems()) { + this.listViewer.insert(item, i++); + } + } + + /** + * Items were moved in the underlying model list. + * Synchronize the list viewer. + */ + protected void itemsMoved(ListMoveEvent e) { + int len = e.getLength(); + Object[] items = new Object[len]; + int offset = e.getSourceIndex(); + for (int i = 0; i < len; i++) { + items[i] = this.listHolder.get(offset + i); + } + this.listViewer.remove(items); + + offset = e.getTargetIndex(); + for (int i = 0; i < len; i++) { + this.listViewer.insert(items[i], offset + i); + } + } + + /** + * The underlying model list was cleared. + * Synchronize the list viewer. + */ + protected void listCleared() { + this.listViewer.refresh(); + } + + /** + * The underlying model list has changed "dramatically". + * Synchronize the list viewer. + */ + protected void listChanged() { + this.listViewer.refresh(); + } + + + // ********** Object overrides ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.listHolder); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTCollectionChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTCollectionChangeListenerWrapper.java new file mode 100644 index 0000000000..1c6a74191c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTCollectionChangeListenerWrapper.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.listeners; + +import org.eclipse.jpt.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.utility.model.listener.CollectionChangeListener; +import org.eclipse.swt.widgets.Display; + +/** + * Wrap another collection change listener and forward events to it on the SWT + * UI thread, asynchronously if necessary. + */ +public class SWTCollectionChangeListenerWrapper + implements CollectionChangeListener +{ + private final CollectionChangeListener listener; + + public SWTCollectionChangeListenerWrapper(CollectionChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void itemsAdded(CollectionAddEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsAdded_(event); + } else { + this.executeOnUIThread(this.buildItemsAddedRunnable(event)); + } + } + + public void itemsRemoved(CollectionRemoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsRemoved_(event); + } else { + this.executeOnUIThread(this.buildItemsRemovedRunnable(event)); + } + } + + public void collectionCleared(CollectionClearEvent event) { + if (this.isExecutingOnUIThread()) { + this.collectionCleared_(event); + } else { + this.executeOnUIThread(this.buildCollectionClearedRunnable(event)); + } + } + + public void collectionChanged(CollectionChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.collectionChanged_(event); + } else { + this.executeOnUIThread(this.buildCollectionChangedRunnable(event)); + } + } + + private Runnable buildItemsAddedRunnable(final CollectionAddEvent event) { + return new Runnable() { + public void run() { + SWTCollectionChangeListenerWrapper.this.itemsAdded_(event); + } + @Override + public String toString() { + return "items added runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsRemovedRunnable(final CollectionRemoveEvent event) { + return new Runnable() { + public void run() { + SWTCollectionChangeListenerWrapper.this.itemsRemoved_(event); + } + @Override + public String toString() { + return "items removed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildCollectionClearedRunnable(final CollectionClearEvent event) { + return new Runnable() { + public void run() { + SWTCollectionChangeListenerWrapper.this.collectionCleared_(event); + } + @Override + public String toString() { + return "collection cleared runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildCollectionChangedRunnable(final CollectionChangeEvent event) { + return new Runnable() { + public void run() { + SWTCollectionChangeListenerWrapper.this.collectionChanged_(event); + } + @Override + public String toString() { + return "collection changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return Display.getCurrent() != null; + } + + /** + * {@link Display#asyncExec(Runnable)} seems to work OK; + * but using {@link Display#syncExec(Runnable)} can somtimes make things + * more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnUIThread(Runnable r) { + Display.getDefault().asyncExec(r); +// Display.getDefault().syncExec(r); + } + + void itemsAdded_(CollectionAddEvent event) { + this.listener.itemsAdded(event); + } + + void itemsRemoved_(CollectionRemoveEvent event) { + this.listener.itemsRemoved(event); + } + + void collectionCleared_(CollectionClearEvent event) { + this.listener.collectionCleared(event); + } + + void collectionChanged_(CollectionChangeEvent event) { + this.listener.collectionChanged(event); + } + + @Override + public String toString() { + return "SWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTListChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTListChangeListenerWrapper.java new file mode 100644 index 0000000000..4b74c4c55d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTListChangeListenerWrapper.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.listeners; + +import org.eclipse.jpt.utility.model.event.ListAddEvent; +import org.eclipse.jpt.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.utility.model.event.ListClearEvent; +import org.eclipse.jpt.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.utility.model.listener.ListChangeListener; +import org.eclipse.swt.widgets.Display; + +/** + * Wrap another list change listener and forward events to it on the SWT + * UI thread, asynchronously if necessary. + */ +public class SWTListChangeListenerWrapper + implements ListChangeListener +{ + private final ListChangeListener listener; + + public SWTListChangeListenerWrapper(ListChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void itemsAdded(ListAddEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsAdded_(event); + } else { + this.executeOnUIThread(this.buildItemsAddedRunnable(event)); + } + } + + public void itemsRemoved(ListRemoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsRemoved_(event); + } else { + this.executeOnUIThread(this.buildItemsRemovedRunnable(event)); + } + } + + public void itemsMoved(ListMoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsMoved_(event); + } else { + this.executeOnUIThread(this.buildItemsMovedRunnable(event)); + } + } + + public void itemsReplaced(ListReplaceEvent event) { + if (this.isExecutingOnUIThread()) { + this.itemsReplaced_(event); + } else { + this.executeOnUIThread(this.buildItemsReplacedRunnable(event)); + } + } + + public void listCleared(ListClearEvent event) { + if (this.isExecutingOnUIThread()) { + this.listCleared_(event); + } else { + this.executeOnUIThread(this.buildListClearedRunnable(event)); + } + } + + public void listChanged(ListChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.listChanged_(event); + } else { + this.executeOnUIThread(this.buildListChangedRunnable(event)); + } + } + + private Runnable buildItemsAddedRunnable(final ListAddEvent event) { + return new Runnable() { + public void run() { + SWTListChangeListenerWrapper.this.itemsAdded_(event); + } + @Override + public String toString() { + return "items added runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsRemovedRunnable(final ListRemoveEvent event) { + return new Runnable() { + public void run() { + SWTListChangeListenerWrapper.this.itemsRemoved_(event); + } + @Override + public String toString() { + return "items removed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsMovedRunnable(final ListMoveEvent event) { + return new Runnable() { + public void run() { + SWTListChangeListenerWrapper.this.itemsMoved_(event); + } + @Override + public String toString() { + return "items moved runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildItemsReplacedRunnable(final ListReplaceEvent event) { + return new Runnable() { + public void run() { + SWTListChangeListenerWrapper.this.itemsReplaced_(event); + } + @Override + public String toString() { + return "items replaced runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildListClearedRunnable(final ListClearEvent event) { + return new Runnable() { + public void run() { + SWTListChangeListenerWrapper.this.listCleared_(event); + } + @Override + public String toString() { + return "list cleared runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildListChangedRunnable(final ListChangeEvent event) { + return new Runnable() { + public void run() { + SWTListChangeListenerWrapper.this.listChanged_(event); + } + @Override + public String toString() { + return "list changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return Display.getCurrent() != null; + } + + /** + * {@link Display#asyncExec(Runnable)} seems to work OK; + * but using {@link Display#syncExec(Runnable)} can somtimes make things + * more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnUIThread(Runnable r) { + Display.getDefault().asyncExec(r); +// Display.getDefault().syncExec(r); + } + + void itemsAdded_(ListAddEvent event) { + this.listener.itemsAdded(event); + } + + void itemsRemoved_(ListRemoveEvent event) { + this.listener.itemsRemoved(event); + } + + void itemsMoved_(ListMoveEvent event) { + this.listener.itemsMoved(event); + } + + void itemsReplaced_(ListReplaceEvent event) { + this.listener.itemsReplaced(event); + } + + void listCleared_(ListClearEvent event) { + this.listener.listCleared(event); + } + + void listChanged_(ListChangeEvent event) { + this.listener.listChanged(event); + } + + @Override + public String toString() { + return "SWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTPropertyChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTPropertyChangeListenerWrapper.java new file mode 100644 index 0000000000..de85205a24 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTPropertyChangeListenerWrapper.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.listeners; + +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.swt.widgets.Display; + +/** + * Wrap another property change listener and forward events to it on the SWT + * UI thread, asynchronously if necessary. If the event arrived on the UI + * thread that is probably because it was initiated by a UI widget; as a + * result, we want to loop back synchronously so the events can be + * short-circuited. (Typically, the adapter(s) between a <em>property</em> and + * its corresponding UI widget are read-write; as opposed to the adapter(s) + * between a <em>collection</em> (or <em>list</em>) and its UI widget, which + * is read-only.) + */ +public class SWTPropertyChangeListenerWrapper + implements PropertyChangeListener +{ + private final PropertyChangeListener listener; + + public SWTPropertyChangeListenerWrapper(PropertyChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void propertyChanged(PropertyChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.propertyChanged_(event); + } else { + this.executeOnUIThread(this.buildPropertyChangedRunnable(event)); + } + } + + private Runnable buildPropertyChangedRunnable(final PropertyChangeEvent event) { + return new Runnable() { + public void run() { + SWTPropertyChangeListenerWrapper.this.propertyChanged_(event); + } + @Override + public String toString() { + return "property changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return Display.getCurrent() != null; + } + + /** + * {@link Display#asyncExec(Runnable)} seems to work OK; + * but using {@link Display#syncExec(Runnable)} can somtimes make things + * more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnUIThread(Runnable r) { + Display.getDefault().asyncExec(r); +// Display.getDefault().syncExec(r); + } + + void propertyChanged_(PropertyChangeEvent event) { + this.listener.propertyChanged(event); + } + + @Override + public String toString() { + return "SWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTStateChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTStateChangeListenerWrapper.java new file mode 100644 index 0000000000..fec9ee3761 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTStateChangeListenerWrapper.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.listeners; + +import org.eclipse.jpt.utility.model.event.StateChangeEvent; +import org.eclipse.jpt.utility.model.listener.StateChangeListener; +import org.eclipse.swt.widgets.Display; + +/** + * Wrap another state change listener and forward events to it on the SWT + * UI thread, asynchronously if necessary. If the event arrived on the UI + * thread that is probably because it was initiated by a UI widget; as a + * result, we want to loop back synchronously so the events can be + * short-circuited. + */ +public class SWTStateChangeListenerWrapper + implements StateChangeListener +{ + private final StateChangeListener listener; + + public SWTStateChangeListenerWrapper(StateChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void stateChanged(StateChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.stateChanged_(event); + } else { + this.executeOnUIThread(this.buildStateChangedRunnable(event)); + } + } + + private Runnable buildStateChangedRunnable(final StateChangeEvent event) { + return new Runnable() { + public void run() { + SWTStateChangeListenerWrapper.this.stateChanged_(event); + } + @Override + public String toString() { + return "state changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return Display.getCurrent() != null; + } + + /** + * {@link Display#asyncExec(Runnable)} seems to work OK; + * but using {@link Display#syncExec(Runnable)} can somtimes make things + * more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnUIThread(Runnable r) { + Display.getDefault().asyncExec(r); +// Display.getDefault().syncExec(r); + } + + void stateChanged_(StateChangeEvent event) { + this.listener.stateChanged(event); + } + + @Override + public String toString() { + return "SWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTTreeChangeListenerWrapper.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTTreeChangeListenerWrapper.java new file mode 100644 index 0000000000..dad875d865 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTTreeChangeListenerWrapper.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.listeners; + +import org.eclipse.jpt.utility.model.event.TreeAddEvent; +import org.eclipse.jpt.utility.model.event.TreeChangeEvent; +import org.eclipse.jpt.utility.model.event.TreeClearEvent; +import org.eclipse.jpt.utility.model.event.TreeRemoveEvent; +import org.eclipse.jpt.utility.model.listener.TreeChangeListener; +import org.eclipse.swt.widgets.Display; + +/** + * Wrap another tree change listener and forward events to it on the SWT + * UI thread, asynchronously if necessary. + */ +public class SWTTreeChangeListenerWrapper + implements TreeChangeListener +{ + private final TreeChangeListener listener; + + public SWTTreeChangeListenerWrapper(TreeChangeListener listener) { + super(); + if (listener == null) { + throw new NullPointerException(); + } + this.listener = listener; + } + + public void nodeAdded(TreeAddEvent event) { + if (this.isExecutingOnUIThread()) { + this.nodeAdded_(event); + } else { + this.executeOnUIThread(this.buildNodeAddedRunnable(event)); + } + } + + public void nodeRemoved(TreeRemoveEvent event) { + if (this.isExecutingOnUIThread()) { + this.nodeRemoved_(event); + } else { + this.executeOnUIThread(this.buildNodeRemovedRunnable(event)); + } + } + + public void treeCleared(TreeClearEvent event) { + if (this.isExecutingOnUIThread()) { + this.treeCleared_(event); + } else { + this.executeOnUIThread(this.buildTreeClearedRunnable(event)); + } + } + + public void treeChanged(TreeChangeEvent event) { + if (this.isExecutingOnUIThread()) { + this.treeChanged_(event); + } else { + this.executeOnUIThread(this.buildTreeChangedRunnable(event)); + } + } + + private Runnable buildNodeAddedRunnable(final TreeAddEvent event) { + return new Runnable() { + public void run() { + SWTTreeChangeListenerWrapper.this.nodeAdded_(event); + } + @Override + public String toString() { + return "node added runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildNodeRemovedRunnable(final TreeRemoveEvent event) { + return new Runnable() { + public void run() { + SWTTreeChangeListenerWrapper.this.nodeRemoved_(event); + } + @Override + public String toString() { + return "node removed runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildTreeClearedRunnable(final TreeClearEvent event) { + return new Runnable() { + public void run() { + SWTTreeChangeListenerWrapper.this.treeCleared_(event); + } + @Override + public String toString() { + return "tree cleared runnable"; //$NON-NLS-1$ + } + }; + } + + private Runnable buildTreeChangedRunnable(final TreeChangeEvent event) { + return new Runnable() { + public void run() { + SWTTreeChangeListenerWrapper.this.treeChanged_(event); + } + @Override + public String toString() { + return "tree changed runnable"; //$NON-NLS-1$ + } + }; + } + + private boolean isExecutingOnUIThread() { + return Display.getCurrent() != null; + } + + /** + * {@link Display#asyncExec(Runnable)} seems to work OK; + * but using {@link Display#syncExec(Runnable)} can somtimes make things + * more predictable when debugging, at the risk of deadlocks. + */ + private void executeOnUIThread(Runnable r) { + Display.getDefault().asyncExec(r); +// Display.getDefault().syncExec(r); + } + + void nodeAdded_(TreeAddEvent event) { + this.listener.nodeAdded(event); + } + + void nodeRemoved_(TreeRemoveEvent event) { + this.listener.nodeRemoved(event); + } + + void treeCleared_(TreeClearEvent event) { + this.listener.treeCleared(event); + } + + void treeChanged_(TreeChangeEvent event) { + this.listener.treeChanged(event); + } + + @Override + public String toString() { + return "SWT(" + this.listener.toString() + ')'; //$NON-NLS-1$ + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/properties/JptProjectPropertiesPage.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/properties/JptProjectPropertiesPage.java new file mode 100644 index 0000000000..30c7aa0bd4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/properties/JptProjectPropertiesPage.java @@ -0,0 +1,432 @@ +/******************************************************************************* + * Copyright (c) 2011 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.properties; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.IWorkspaceRunnable; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jpt.common.core.JptCommonCorePlugin; +import org.eclipse.jpt.utility.internal.ArrayTools; +import org.eclipse.jpt.utility.internal.CollectionTools; +import org.eclipse.jpt.utility.internal.model.value.BufferedWritablePropertyValueModel; +import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.listener.ChangeListener; +import org.eclipse.jpt.utility.model.listener.SimpleChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.jst.common.project.facet.core.libprov.IPropertyChangeListener; +import org.eclipse.jst.common.project.facet.core.libprov.LibraryInstallDelegate; +import org.eclipse.jst.common.project.facet.ui.libprov.LibraryFacetPropertyPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Link; +import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer; +import org.eclipse.wst.common.project.facet.core.IFacetedProject; +import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion; +import org.eclipse.wst.common.project.facet.ui.internal.FacetsPropertyPage; + + +public abstract class JptProjectPropertiesPage + extends LibraryFacetPropertyPage { + + protected final WritablePropertyValueModel<IProject> projectModel; + protected final BufferedWritablePropertyValueModel.Trigger trigger; + + protected final ChangeListener validationListener; + + + public JptProjectPropertiesPage() { + super(); + + this.projectModel = new SimplePropertyValueModel<IProject>(); + this.trigger = new BufferedWritablePropertyValueModel.Trigger(); + + buildModels(); + + this.validationListener = this.buildValidationListener(); + } + + + /** + * Build any additional models needed by this page. The project model has been created at this + * point. + */ + protected abstract void buildModels(); + + + // ********** convenience methods ********** + + protected static boolean flagIsSet(PropertyValueModel<Boolean> flagModel) { + Boolean flag = flagModel.getValue(); + return (flag != null) && flag.booleanValue(); + } + + + // ********** LibraryFacetPropertyPage implementation ********** + + @Override + protected LibraryInstallDelegate createLibraryInstallDelegate(IFacetedProject project, IProjectFacetVersion fv) { + LibraryInstallDelegate lid = new LibraryInstallDelegate(project, fv, null); + lid.addListener(buildLibraryProviderListener()); + return lid; + } + + protected IPropertyChangeListener buildLibraryProviderListener() { + return new IPropertyChangeListener() { + public void propertyChanged(String property, Object oldValue, Object newValue ) { + if (LibraryInstallDelegate.PROP_AVAILABLE_PROVIDERS.equals(property)) { + adjustLibraryProviders(); + } + } + }; + } + + protected abstract void adjustLibraryProviders(); + + + // ********** page ********** + + @Override + protected Control createPageContents(Composite parent) { + if (this.projectModel.getValue() != null) { + disengageListeners(); + } + + this.projectModel.setValue(getProject()); + + Composite composite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.marginWidth = 0; + layout.marginHeight = 0; + composite.setLayout(layout); + + createWidgets(composite); + + Dialog.applyDialogFont(composite); + + adjustLibraryProviders(); + + engageListeners(); + updateValidation(); + + return composite; + } + + /** + * Build specific widgets. Layout and validation will be taken care of. + */ + protected abstract void createWidgets(Composite parent); + + protected void engageListeners() { + engageValidationListener(); + } + + protected void disengageListeners() { + disengageValidationListener(); + } + + protected Link buildFacetsPageLink(Composite parent, String text) { + Link facetsPageLink = buildLink(parent, text); + facetsPageLink.addSelectionListener(buildFacetsPageLinkListener()); // the link will be GCed + return facetsPageLink; + } + + private SelectionListener buildFacetsPageLinkListener() { + return new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + openProjectFacetsPage(); + } + @Override + public String toString() { + return "facets page link listener"; //$NON-NLS-1$ + } + }; + } + + protected void openProjectFacetsPage() { + ((IWorkbenchPreferenceContainer)getContainer()).openPage(FacetsPropertyPage.ID, null); + } + + /** + * Don't allow {@link org.eclipse.jface.preference.PreferencePage#computeSize()} + * to cache the page's size, since the size of the "Library" panel can + * change depending on the user's selection from the drop-down list. + */ + @Override + public Point computeSize() { + return this.doComputeSize(); + } + + + // ********** widgets ********** + + protected Button buildCheckBox(Composite parent, int horizontalSpan, String text) { + return buildButton(parent, horizontalSpan, text, SWT.CHECK); + } + + protected Button buildRadioButton(Composite parent, int horizontalSpan, String text) { + return buildButton(parent, horizontalSpan, text, SWT.RADIO); + } + + protected Button buildButton(Composite parent, int horizontalSpan, String text, int style) { + Button button = new Button(parent, SWT.NONE | style); + button.setText(text); + GridData gd = new GridData(); + gd.horizontalSpan = horizontalSpan; + button.setLayoutData(gd); + return button; + } + + protected Combo buildDropDown(Composite parent) { + return buildDropDown(parent, 1); + } + + protected Combo buildDropDown(Composite parent, int horizontalSpan) { + Combo combo = new Combo(parent, SWT.READ_ONLY); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = horizontalSpan; + combo.setLayoutData(gd); + return combo; + } + + protected Label buildLabel(Composite parent, String text) { + Label label = new Label(parent, SWT.LEFT); + label.setText(text); + GridData gd = new GridData(); + gd.horizontalSpan = 1; + label.setLayoutData(gd); + return label; + } + + protected Link buildLink(Composite parent, String text) { + Link link = new Link(parent, SWT.NONE); + GridData data = new GridData(GridData.END, GridData.CENTER, false, false); + data.horizontalSpan = 2; + link.setLayoutData(data); + link.setText(text); + return link; + } + + + // ********** OK/Revert/Apply behavior ********** + + @Override + public boolean performOk() { + super.performOk(); + + try { + // true=fork; false=uncancellable + this.buildOkProgressMonitorDialog().run(true, false, this.buildOkRunnableWithProgress()); + } + catch (InterruptedException ex) { + return false; + } + catch (InvocationTargetException ex) { + throw new RuntimeException(ex.getTargetException()); + } + + return true; + } + + private IRunnableContext buildOkProgressMonitorDialog() { + return new ProgressMonitorDialog(this.getShell()); + } + + private IRunnableWithProgress buildOkRunnableWithProgress() { + return new IRunnableWithProgress() { + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + IWorkspace ws = ResourcesPlugin.getWorkspace(); + try { + // the build we execute in #performOk_() locks the workspace root, + // so we need to use the workspace root as our scheduling rule here + ws.run( + buildOkWorkspaceRunnable(), + ws.getRoot(), + IWorkspace.AVOID_UPDATE, + monitor); + } + catch (CoreException ex) { + throw new InvocationTargetException(ex); + } + } + }; + } + + /* private */ IWorkspaceRunnable buildOkWorkspaceRunnable() { + return new IWorkspaceRunnable() { + public void run(IProgressMonitor monitor) throws CoreException { + performOk_(monitor); + } + }; + } + + // ********** OK/Revert/Apply behavior ********** + + void performOk_(IProgressMonitor monitor) throws CoreException { + if (isBuffering()) { + boolean rebuild = projectRebuildRequired(); + this.trigger.accept(); + if (rebuild) { + rebuildProject(); + } + this.getProject().build(IncrementalProjectBuilder.FULL_BUILD, monitor); + } + } + + protected abstract boolean projectRebuildRequired(); + + protected abstract void rebuildProject(); + + /** + * Return whether any of the models are buffering a change. + */ + private boolean isBuffering() { + for (BufferedWritablePropertyValueModel<?> model : buildBufferedModels()) { + if (model.isBuffering()) { + return true; + } + } + return false; + } + + protected abstract BufferedWritablePropertyValueModel<?>[] buildBufferedModels(); + + @Override + protected void performDefaults() { + super.performDefaults(); + this.trigger.reset(); + } + + + // ********** dispose ********** + + @Override + public void dispose() { + disengageListeners(); + super.dispose(); + } + + + // ********** validation ********** + + private ChangeListener buildValidationListener() { + return new SimpleChangeListener() { + @Override + protected void modelChanged() { + validate(); + } + @Override + public String toString() { + return "validation listener"; //$NON-NLS-1$ + } + }; + } + + protected void validate() { + if ( ! getControl().isDisposed()) { + updateValidation(); + } + } + + private void engageValidationListener() { + for (Model model : buildValidationModels()) { + model.addChangeListener(this.validationListener); + } + } + + protected abstract Model[] buildValidationModels(); + + private void disengageValidationListener() { + for (Model model : buildReverseValidationModels()) { + model.removeChangeListener(this.validationListener); + } + } + + protected Model[] buildReverseValidationModels() { + return ArrayTools.reverse(buildValidationModels()); + } + + protected static final Integer ERROR_STATUS = Integer.valueOf(IStatus.ERROR); + protected static final Integer WARNING_STATUS = Integer.valueOf(IStatus.WARNING); + protected static final Integer INFO_STATUS = Integer.valueOf(IStatus.INFO); + protected static final Integer OK_STATUS = Integer.valueOf(IStatus.OK); + + protected IStatus buildInfoStatus(String message) { + return this.buildStatus(IStatus.INFO, message); + } + + protected IStatus buildWarningStatus(String message) { + return this.buildStatus(IStatus.WARNING, message); + } + + protected IStatus buildErrorStatus(String message) { + return this.buildStatus(IStatus.ERROR, message); + } + + protected IStatus buildStatus(int severity, String message) { + return new Status(severity, JptCommonCorePlugin.PLUGIN_ID, message); + } + + @Override + protected IStatus performValidation() { + HashMap<Integer, ArrayList<IStatus>> statuses = new HashMap<Integer, ArrayList<IStatus>>(); + statuses.put(ERROR_STATUS, new ArrayList<IStatus>()); + statuses.put(WARNING_STATUS, new ArrayList<IStatus>()); + statuses.put(INFO_STATUS, new ArrayList<IStatus>()); + statuses.put(OK_STATUS, CollectionTools.list(Status.OK_STATUS)); + + performValidation(statuses); + + if ( ! statuses.get(ERROR_STATUS).isEmpty()) { + return statuses.get(ERROR_STATUS).get(0); + } + else if ( ! statuses.get(WARNING_STATUS).isEmpty()) { + return statuses.get(WARNING_STATUS).get(0); + } + else if ( ! statuses.get(INFO_STATUS).isEmpty()) { + return statuses.get(INFO_STATUS).get(0); + } + else { + return statuses.get(OK_STATUS).get(0); + } + } + + protected void performValidation(Map<Integer, ArrayList<IStatus>> statuses) { + /* library provider */ + IStatus lpStatus = super.performValidation(); + statuses.get(Integer.valueOf(lpStatus.getSeverity())).add(lpStatus); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/AbstractComboModelAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/AbstractComboModelAdapter.java new file mode 100644 index 0000000000..268742ad66 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/AbstractComboModelAdapter.java @@ -0,0 +1,699 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.swt; + +import java.util.EventListener; +import java.util.EventObject; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jpt.common.ui.internal.listeners.SWTListChangeListenerWrapper; +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.ArrayTools; +import org.eclipse.jpt.utility.internal.ListenerList; +import org.eclipse.jpt.utility.internal.StringConverter; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.model.event.ListAddEvent; +import org.eclipse.jpt.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.utility.model.event.ListClearEvent; +import org.eclipse.jpt.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; + +/** + * This adapter provides a more object-oriented interface to the items and + * selected item in a combo. + * <p> + * <b>listHolder</b> contains the items in the combo.<br> + * <b>selectedItemHolder</b> contains the items in 'listHolder' that are + * selected in the combo. + * + * @param <E> The type of the items in <b>listHolder</b> + * @see ComboModelAdapter + * @see CComboModelAdapter + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public abstract class AbstractComboModelAdapter<E> { + + // ********** model ********** + /** + * A value model on the underlying model list. + */ + protected final ListValueModel<E> listHolder; + + /** + * A listener that allows us to synchronize the combo's contents with + * the model list. + */ + protected final ListChangeListener listChangeListener; + + /** + * A value model on the underlying model selection. + */ + protected final WritablePropertyValueModel<E> selectedItemHolder; + + /** + * A listener that allows us to synchronize the combo's selection with the + * model selection. + */ + protected final PropertyChangeListener selectedItemChangeListener; + + /** + * A converter that converts items in the model list + * to strings that can be put in the combo. + */ + protected StringConverter<E> stringConverter; + + // ********** UI ********** + /** + * The combo we keep synchronized with the model list. + */ + protected final ComboHolder comboHolder; + + /** + * A listener that allows us to synchronize our selection list holder + * with the combo's text. + */ + protected ModifyListener comboModifyListener; + + /** + * A listener that allows us to synchronize our selection list holder + * with the combo's selection. + */ + protected SelectionListener comboSelectionListener; + + /** + * Clients that are interested in selection change events. + */ + @SuppressWarnings("unchecked") + protected final ListenerList<SelectionChangeListener> selectionChangeListenerList; + + /** + * Clients that are interested in double click events. + */ + @SuppressWarnings("unchecked") + protected final ListenerList<DoubleClickListener> doubleClickListenerList; + + /** + * A listener that allows us to stop listening to stuff when the combo + * is disposed. + */ + protected final DisposeListener comboDisposeListener; + + + // ********** constructors ********** + + /** + * Constructor - the list holder, selections holder, combo, and + * string converter are required. + */ + protected AbstractComboModelAdapter( + ListValueModel<E> listHolder, + WritablePropertyValueModel<E> selectedItemHolder, + ComboHolder comboHolder, + StringConverter<E> stringConverter) + { + super(); + + Assert.isNotNull(listHolder, "The holder of the items"); + Assert.isNotNull(selectedItemHolder, "The holder of the selected item cannot be null"); + Assert.isNotNull(comboHolder, "The holder of the combo widget cannot be null"); + Assert.isNotNull(stringConverter, "The string converter cannot be null"); + + this.listHolder = listHolder; + this.selectedItemHolder = selectedItemHolder; + this.comboHolder = comboHolder; + this.stringConverter = stringConverter; + + this.listChangeListener = this.buildListChangeListener(); + this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + + this.selectedItemChangeListener = this.buildSelectedItemChangeListener(); + this.selectedItemHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.selectedItemChangeListener); + + if (this.comboHolder.isEditable()) { + this.comboModifyListener = this.buildComboModifyListener(); + this.comboHolder.addModifyListener(this.comboModifyListener); + } + else { + this.comboSelectionListener = this.buildComboSelectionListener(); + this.comboHolder.addSelectionListener(this.comboSelectionListener); + } + + this.selectionChangeListenerList = this.buildSelectionChangeListenerList(); + this.doubleClickListenerList = this.buildDoubleClickListenerList(); + + this.comboDisposeListener = this.buildComboDisposeListener(); + this.comboHolder.addDisposeListener(this.comboDisposeListener); + + this.synchronizeCombo(); + } + + + // ********** initialization ********** + + protected ListChangeListener buildListChangeListener() { + return new SWTListChangeListenerWrapper(this.buildListChangeListener_()); + } + + protected ListChangeListener buildListChangeListener_() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + AbstractComboModelAdapter.this.listItemsAdded(event); + } + public void itemsRemoved(ListRemoveEvent event) { + AbstractComboModelAdapter.this.listItemsRemoved(event); + } + public void itemsMoved(ListMoveEvent event) { + AbstractComboModelAdapter.this.listItemsMoved(event); + } + public void itemsReplaced(ListReplaceEvent event) { + AbstractComboModelAdapter.this.listItemsReplaced(event); + } + public void listCleared(ListClearEvent event) { + AbstractComboModelAdapter.this.listCleared(event); + } + public void listChanged(ListChangeEvent event) { + AbstractComboModelAdapter.this.listChanged(event); + } + @Override + public String toString() { + return "list listener"; + } + }; + } + + protected PropertyChangeListener buildSelectedItemChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildSelectedItemChangeListener_()); + } + + protected PropertyChangeListener buildSelectedItemChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent e) { + AbstractComboModelAdapter.this.selectedItemChanged(e); + } + }; + } + + protected ModifyListener buildComboModifyListener() { + return new ModifyListener() { + public void modifyText(ModifyEvent event) { + AbstractComboModelAdapter.this.comboSelectionChanged(event); + } + + @Override + public String toString() { + return "combo modify listener"; + } + }; + } + + protected SelectionListener buildComboSelectionListener() { + return new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + AbstractComboModelAdapter.this.comboSelectionChanged(event); + } + + @Override + public String toString() { + return "combo modify listener"; + } + }; + } + + @SuppressWarnings("unchecked") + protected ListenerList<DoubleClickListener> buildDoubleClickListenerList() { + return new ListenerList<DoubleClickListener>(DoubleClickListener.class); + } + + @SuppressWarnings("unchecked") + protected ListenerList<SelectionChangeListener> buildSelectionChangeListenerList() { + return new ListenerList<SelectionChangeListener>(SelectionChangeListener.class); + } + + protected DisposeListener buildComboDisposeListener() { + return new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + AbstractComboModelAdapter.this.comboDisposed(event); + } + + @Override + public String toString() { + return "combo dispose listener"; + } + }; + } + + protected void synchronizeCombo() { + this.synchronizeComboItems(); + this.synchronizeComboSelection(); + } + + + // ********** string converter ********** + + public void setStringConverter(StringConverter<E> stringConverter) { + Assert.isNotNull(stringConverter, "The StringConverter cannot be null"); + this.stringConverter = stringConverter; + this.synchronizeCombo(); + } + + + // ********** list ********** + + /** + * Use the string converter to convert the specified item to a + * string that can be added to the combo. + */ + protected String convert(E item) { + return this.stringConverter.convertToString(item); + } + + /** + * Brute force synchronization of combo with the model list. + */ + protected void synchronizeComboItems() { + if (this.comboHolder.isDisposed()) { + return; + } + int len = this.listHolder.size(); + String[] items = new String[len]; + for (int index = 0; index < len; index++) { + items[index] = this.convert(this.listHolder.get(index)); + } + try { + this.comboHolder.setPopulating(true); + this.comboHolder.setItems(items); + } + finally { + this.comboHolder.setPopulating(false); + } + } + + /** + * The model has changed - synchronize the combo. + */ + protected void listItemsAdded(ListAddEvent event) { + if (this.comboHolder.isDisposed()) { + return; + } + + int count = this.comboHolder.getItemCount(); + int index = event.getIndex(); + + for (E item : this.getItems(event)) { + this.comboHolder.add(this.convert(item), index++); + } + + // When the combo is populated, it's possible the selection was already + // set but no items was found, resync the selected item + synchronizeComboSelection(); + } + + /** + * The model has changed - synchronize the combo. + */ + protected void listItemsRemoved(ListRemoveEvent event) { + if (this.comboHolder.isDisposed()) { + return; + } + this.comboHolder.remove(event.getIndex(), event.getIndex() + event.getItemsSize() - 1); + this.synchronizeComboSelection(); + } + + /** + * The model has changed - synchronize the combo. + */ + protected void listItemsMoved(ListMoveEvent event) { + if (this.comboHolder.isDisposed()) { + return; + } + int target = event.getTargetIndex(); + int source = event.getSourceIndex(); + int len = event.getLength(); + int loStart = Math.min(target, source); + int hiStart = Math.max(target, source); + // make a copy of the affected items... + String[] subArray = ArrayTools.subArray(this.comboHolder.getItems(), loStart, hiStart + len - loStart); + // ...move them around... + subArray = ArrayTools.move(subArray, target - loStart, source - loStart, len); + // ...and then put them back + for (int index = 0; index < subArray.length; index++) { + this.comboHolder.setItem(loStart + index, subArray[index]); + } + } + + /** + * The model has changed - synchronize the combo. + */ + protected void listItemsReplaced(ListReplaceEvent event) { + if (this.comboHolder.isDisposed()) { + return; + } + int index = event.getIndex(); + int selectionIndex = this.comboHolder.getSelectionIndex(); + //fixing bug 269100 by setting the populating flag to true + this.comboHolder.setPopulating(true); + try { + for (E item : this.getNewItems(event)) { + this.comboHolder.setItem(index++, this.convert(item)); + } + if (selectionIndex == 0) { + this.comboHolder.setText(this.comboHolder.getItems()[0]); + } + } + finally { + this.comboHolder.setPopulating(false); + } + } + + /** + * The model has changed - synchronize the combo. + */ + protected void listCleared(ListClearEvent event) { + if (this.comboHolder.isDisposed()) { + return; + } + this.comboHolder.setPopulating(true); + try { + this.comboHolder.removeAll(); + } + finally { + this.comboHolder.setPopulating(false); + } + } + + /** + * The model has changed - synchronize the combo. + */ + protected void listChanged(ListChangeEvent event) { + this.synchronizeCombo(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable<E> getItems(ListAddEvent event) { + return (Iterable<E>) event.getItems(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable<E> getNewItems(ListReplaceEvent event) { + return (Iterable<E>) event.getNewItems(); + } + + + // ********** selected items ********** + + protected int indexOf(E item) { + int length = this.listHolder.size(); + for (int index = 0; index < length; index++) { + if (valuesAreEqual(this.listHolder.get(index), item)) { + return index; + } + } + return -1; + } + + protected void synchronizeComboSelection() { + if (this.comboHolder.isDisposed() || this.comboHolder.isPopulating()) { + return; + } + + E selectedValue = this.selectedItemHolder.getValue(); + if (this.comboHolder.getText().equals(selectedValue)) { + //if the selection is still the same, don't reset it + return; + } + this.comboHolder.setPopulating(true); + try { + this.comboHolder.deselectAll(); + String selectedItem = this.convert(selectedValue); + if (selectedItem == null) { + selectedItem = ""; + } + this.comboHolder.setText(selectedItem); + this.notifyListeners(selectedValue); + } + finally { + this.comboHolder.setPopulating(false); + } + } + + protected void selectedItemChanged(PropertyChangeEvent event) { + this.synchronizeComboSelection(); + } + + /** + * Return whether the values are equal, with the appropriate null checks. + * Convenience method for checking whether an attribute value has changed. + */ + protected final boolean valuesAreEqual(Object value1, Object value2) { + if ((value1 == null) && (value2 == null)) { + return true; // both are null + } + if ((value1 == null) || (value2 == null)) { + return false; // one is null but the other is not + } + return value1.equals(value2); + } + + // ********** combo events ********** + + protected void comboSelectionChanged(SelectionEvent event) { + this.selectionChanged(); + } + + protected void comboSelectionChanged(ModifyEvent event) { + this.selectionChanged(); + } + + protected void selectionChanged() { + if (!this.comboHolder.isPopulating()) { + E selectedItem = this.selectedItem(); + this.comboHolder.setPopulating(true); + try { + this.selectedItemHolder.setValue(selectedItem); + this.notifyListeners(selectedItem); + } + finally { + this.comboHolder.setPopulating(false); + } + } + } + + private void notifyListeners(E selectedItem) { + if (this.selectionChangeListenerList.size() > 0) { + SelectionChangeEvent<E> scEvent = new SelectionChangeEvent<E>(this, selectedItem); + for (SelectionChangeListener<E> selectionChangeListener : this.selectionChangeListenerList.getListeners()) { + selectionChangeListener.selectionChanged(scEvent); + } + } + } + + @SuppressWarnings("unchecked") + protected E selectedItem() { + if (this.comboHolder.isDisposed()) { + return null; + } + + if (this.comboHolder.isEditable()) { + String text = this.comboHolder.getText(); + + if (text.length() == 0) { + return null; + } + + for (int index = this.listHolder.size(); --index >= 0; ) { + E item = this.listHolder.get(index); + String value = this.convert(item); + if (valuesAreEqual(text, value)) { + return item; + } + } + + // TODO: Find a way to prevent this type cast (it'll work if E is + // String but it won't work if E is something else), maybe use a + // BidiStringConverter instead of StringConverter + try { + return (E) text; + } + catch (ClassCastException e) { + return null; + } + } + + int index = this.comboHolder.getSelectionIndex(); + + if (index == -1) { + return null; + } + + return this.listHolder.get(index); + } + + protected void comboDoubleClicked(SelectionEvent event) { + if (this.comboHolder.isDisposed()) { + return; + } + if (this.doubleClickListenerList.size() > 0) { + // there should be only a single item selected during a double-click(?) + E selection = this.listHolder.get(this.comboHolder.getSelectionIndex()); + DoubleClickEvent<E> dcEvent = new DoubleClickEvent<E>(this, selection); + for (DoubleClickListener<E> doubleClickListener : this.doubleClickListenerList.getListeners()) { + doubleClickListener.doubleClick(dcEvent); + } + } + } + + + // ********** dispose ********** + + protected void comboDisposed(DisposeEvent event) { + // the combo is not yet "disposed" when we receive this event + // so we can still remove our listeners + this.comboHolder.removeDisposeListener(this.comboDisposeListener); + if (this.comboHolder.isEditable()) { + this.comboHolder.removeModifyListener(this.comboModifyListener); + } + else { + this.comboHolder.removeSelectionListener(this.comboSelectionListener); + } + this.selectedItemHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.selectedItemChangeListener); + this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.listHolder); + } + + + // ********** double click support ********** + + public void addDoubleClickListener(DoubleClickListener<E> listener) { + this.doubleClickListenerList.add(listener); + } + + public void removeDoubleClickListener(DoubleClickListener<E> listener) { + this.doubleClickListenerList.remove(listener); + } + + public interface DoubleClickListener<E> extends EventListener { + void doubleClick(DoubleClickEvent<E> event); + } + + public static class DoubleClickEvent<E> extends EventObject { + private final E selection; + private static final long serialVersionUID = 1L; + + protected DoubleClickEvent(AbstractComboModelAdapter<E> source, E selection) { + super(source); + if (selection == null) { + throw new NullPointerException(); + } + this.selection = selection; + } + + @Override + @SuppressWarnings("unchecked") + public AbstractComboModelAdapter<E> getSource() { + return (AbstractComboModelAdapter<E>) super.getSource(); + } + + public E selection() { + return this.selection; + } + } + + + // ********** selection support ********** + + public void addSelectionChangeListener(SelectionChangeListener<E> listener) { + this.selectionChangeListenerList.add(listener); + } + + public void removeSelectionChangeListener(SelectionChangeListener<E> listener) { + this.selectionChangeListenerList.remove(listener); + } + + public interface SelectionChangeListener<E> extends EventListener { + void selectionChanged(SelectionChangeEvent<E> event); + } + + public static class SelectionChangeEvent<E> extends EventObject { + private final E selectedItem; + private static final long serialVersionUID = 1L; + + protected SelectionChangeEvent(AbstractComboModelAdapter<E> source, E selectedItem) { + super(source); + this.selectedItem = selectedItem; + } + + @Override + @SuppressWarnings("unchecked") + public AbstractComboModelAdapter<E> getSource() { + return (AbstractComboModelAdapter<E>) super.getSource(); + } + + public E selectedItem() { + return this.selectedItem; + } + } + + // ********** Internal member ********** + + /** + * This holder is required for supporting <code>Combo</code> and + * <code>CCombo</code> transparently. + */ + protected static interface ComboHolder { + void add(String item, int index); + void addDisposeListener(DisposeListener disposeListener); + void addModifyListener(ModifyListener modifyListener); + void addSelectionListener(SelectionListener selectionListener); + void deselectAll(); + int getItemCount(); + String[] getItems(); + int getSelectionIndex(); + String getText(); + boolean isDisposed(); + boolean isEditable(); + boolean isPopulating(); + void removeDisposeListener(DisposeListener disposeListener); + void removeModifyListener(ModifyListener modifyListener); + void removeSelectionListener(SelectionListener selectionListener); + void setItem(int index, String item); + void setItems(String[] items); + void setPopulating(boolean populating); + void setText(String item); + void remove(int start, int end); + void removeAll(); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/ColumnAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/ColumnAdapter.java new file mode 100644 index 0000000000..189d4ed2e4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/ColumnAdapter.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.swt; + +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; + +/** + * This adapter is used by the table model adapter to convert a model object + * into the models used for each of the cells for the object's corresponding row + * in the table. + * + * @version 2.0 + * @since 2.0 + */ +public interface ColumnAdapter<V> { + + /** + * Return the cell models for the specified subject + * that corresponds to a single row in the table. + */ + WritablePropertyValueModel<?>[] cellModels(V subject); + + /** + * Returns the number of columns in the table. Typically this is static. + * + * @return The number of columns + */ + int columnCount(); + + /** + * Returns the name of the column at the specified index. + * + * @param columnIndex The index of the column to retrieve its display text + * @return The display text of the column + */ + String columnName(int columnIndex); + + /** + * Returns whether the specified column is editable. Typically this is the + * same for every row. + * + * @param columnIndex The index of the column for which we determine if + * the content can be modified + * @return <code>true</code> to allow editing of the cell at the given + * column index; <code>false</code> to keep it not editable + */ +// boolean columnIsEditable(int columnIndex); +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/ComboModelAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/ComboModelAdapter.java new file mode 100644 index 0000000000..636c8ea8cb --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/ComboModelAdapter.java @@ -0,0 +1,210 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.swt; + +import org.eclipse.jpt.utility.internal.StringConverter; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Combo; + +/** + * This adapter provides a more object-oriented interface to the items and + * selected item in a <code>Combo</code>. + * <p> + * <b>listHolder</b> contains the items in the <code>Combo</code>.<br> + * <b>selectedItemHolder</b> contains the items in 'listHolder' that are + * selected in the <code>Combo</code>. + * + * @param <E> The type of the items in <b>listHolder</b> + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public class ComboModelAdapter<E> extends AbstractComboModelAdapter<E> { + + // ********** static methods ********** + + /** + * Adapt the specified model list and selection to the specified combo. + * Use the default string converter to convert the model items to strings + * to be displayed in the combo, which calls #toString() on the + * items in the model list. + */ + public static <T> ComboModelAdapter<T> adapt( + ListValueModel<T> listHolder, + WritablePropertyValueModel<T> selectedItemHolder, + Combo combo) + { + return adapt( + listHolder, + selectedItemHolder, + combo, + StringConverter.Default.<T>instance() + ); + } + + /** + * Adapt the specified model list and selection to the specified combo. + * Use the specified string converter to convert the model items to strings + * to be displayed in the combo. + */ + public static <T> ComboModelAdapter<T> adapt( + ListValueModel<T> listHolder, + WritablePropertyValueModel<T> selectedItemHolder, + Combo combo, + StringConverter<T> stringConverter) + { + return new ComboModelAdapter<T>( + listHolder, + selectedItemHolder, + combo, + stringConverter + ); + } + + + // ********** constructors ********** + + /** + * Constructor - the list holder, selections holder, combo, and + * string converter are required. + */ + protected ComboModelAdapter( + ListValueModel<E> listHolder, + WritablePropertyValueModel<E> selectedItemHolder, + Combo combo, + StringConverter<E> stringConverter) + { + super(listHolder, + selectedItemHolder, + new SWTComboHolder(combo), + stringConverter); + } + + + // ********** Internal member ********** + + private static class SWTComboHolder implements ComboHolder { + private final Combo combo; + private final boolean editable; + private String selectedItem; + + SWTComboHolder(Combo combo) { + super(); + this.combo = combo; + this.editable = (combo.getStyle() & SWT.READ_ONLY) == 0; + } + + public void add(String item, int index) { + this.combo.add(item, index); + + // It is possible the selected item was set before the combo is being + // populated, update the selected item if it's matches the item being + // added + if ((this.selectedItem != null) && this.selectedItem.equals(item)) { + this.setText(this.selectedItem); + this.selectedItem = null; + } + } + + public void addDisposeListener(DisposeListener disposeListener) { + this.combo.addDisposeListener(disposeListener); + } + + public void addModifyListener(ModifyListener modifyListener) { + this.combo.addModifyListener(modifyListener); + } + + public void addSelectionListener(SelectionListener selectionListener) { + this.combo.addSelectionListener(selectionListener); + } + + public void deselectAll() { + this.combo.deselectAll(); + } + + public int getItemCount() { + return this.combo.getItemCount(); + } + + public String[] getItems() { + return this.combo.getItems(); + } + + public int getSelectionIndex() { + return this.combo.getSelectionIndex(); + } + + public String getText() { + return this.combo.getText(); + } + + public boolean isDisposed() { + return this.combo.isDisposed(); + } + + public boolean isEditable() { + return this.editable; + } + + public boolean isPopulating() { + return this.combo.getData("populating") == Boolean.TRUE; + } + + public void remove(int start, int end) { + this.combo.remove(start, end); + } + + public void removeAll() { + this.combo.removeAll(); + } + + public void removeDisposeListener(DisposeListener disposeListener) { + this.combo.removeDisposeListener(disposeListener); + } + + public void removeModifyListener(ModifyListener modifyListener) { + this.combo.removeModifyListener(modifyListener); + } + + public void removeSelectionListener(SelectionListener selectionListener) { + this.combo.removeSelectionListener(selectionListener); + } + + public void setItem(int index, String item) { + this.combo.setItem(index, item); + } + + public void setItems(String[] items) { + this.combo.setItems(items); + } + + public void setPopulating(boolean populating) { + this.combo.setData("populating", Boolean.valueOf(populating)); + } + + public void setText(String item) { + + // Keep track of the selected item since it's possible the selected + // item is before the combo is populated + if (this.combo.getItemCount() == 0) { + this.selectedItem = item; + } + else { + this.selectedItem = null; + } + this.combo.setText(item); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/DateTimeModelAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/DateTimeModelAdapter.java new file mode 100644 index 0000000000..9f2ab7b0bd --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/DateTimeModelAdapter.java @@ -0,0 +1,352 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.swt; + +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.DateTime; + +/** + * This adapter can be used to keep a DateTime widget in synch with + * model integers hours, minutes, and seconds. Has default hours, + * minutes and seconds of 0 which corresponds to 12:00:00 AM. This model + * adapter can only be used for a DateTime widget with the style SWT.TIME + */ +@SuppressWarnings("nls") +public class DateTimeModelAdapter { + + /** + * A value model on the underlying model hours integer. + */ + protected final WritablePropertyValueModel<Integer> hoursHolder; + + /** + * A value model on the underlying model minutes integer. + */ + protected final WritablePropertyValueModel<Integer> minutesHolder; + + /** + * A value model on the underlying model seconds integer. + */ + protected final WritablePropertyValueModel<Integer> secondsHolder; + + /** + * A listener that allows us to synchronize the dateTime's selection state with + * the model hours integer. + */ + protected final PropertyChangeListener hoursPropertyChangeListener; + + /** + * A listener that allows us to synchronize the dateTime's selection state with + * the model minutes integer. + */ + protected final PropertyChangeListener minutesPropertyChangeListener; + + /** + * A listener that allows us to synchronize the dateTime's selection state with + * the model seconds integer. + */ + protected final PropertyChangeListener secondsPropertyChangeListener; + + /** + * The dateTime we keep synchronized with the model integers. + */ + protected final DateTime dateTime; + + /** + * A listener that allows us to synchronize our selection number holder + * with the spinner's value. + */ + protected final SelectionListener dateTimeSelectionListener; + + /** + * A listener that allows us to stop listening to stuff when the dateTime + * is disposed. + */ + protected final DisposeListener dateTimeDisposeListener; + + /** + * This lock is used to prevent the listeners to be notified when the value + * changes from the spinner or from the holder. + */ + private boolean locked; + + // ********** static methods ********** + + /** + * Adapt the specified model integer holders to the specified dateTime. + */ + public static DateTimeModelAdapter adapt( + WritablePropertyValueModel<Integer> hoursHolder, + WritablePropertyValueModel<Integer> minutesHolder, + WritablePropertyValueModel<Integer> secondsHolder, + DateTime dateTime) + { + return new DateTimeModelAdapter(hoursHolder, minutesHolder, secondsHolder, dateTime); + } + + + // ********** constructors ********** + + /** + * Constructor - the hoursHolder, minutesHolder, secondsHolder, and dateTime are required + */ + protected DateTimeModelAdapter(WritablePropertyValueModel<Integer> hoursHolder, + WritablePropertyValueModel<Integer> minutesHolder, + WritablePropertyValueModel<Integer> secondsHolder, + DateTime dateTime) { + super(); + if ((hoursHolder == null) + || (minutesHolder == null) + || (secondsHolder == null) + || (dateTime == null)) { + throw new NullPointerException(); + } + this.hoursHolder = hoursHolder; + this.minutesHolder = minutesHolder; + this.secondsHolder = secondsHolder; + this.dateTime = dateTime; + + this.hoursPropertyChangeListener = this.buildHoursPropertyChangeListener(); + this.hoursHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.hoursPropertyChangeListener); + + this.minutesPropertyChangeListener = this.buildMinutesPropertyChangeListener(); + this.minutesHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.minutesPropertyChangeListener); + + this.secondsPropertyChangeListener = this.buildSecondsPropertyChangeListener(); + this.secondsHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.secondsPropertyChangeListener); + + this.dateTimeSelectionListener = this.buildDateTimeSelectionListener(); + this.dateTime.addSelectionListener(this.dateTimeSelectionListener); + + this.dateTimeDisposeListener = this.buildDateTimeDisposeListener(); + this.dateTime.addDisposeListener(this.dateTimeDisposeListener); + + this.updateDateTimeHours(hoursHolder.getValue()); + this.updateDateTimeMinutes(minutesHolder.getValue()); + this.updateDateTimeSeconds(secondsHolder.getValue()); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildHoursPropertyChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildHoursPropertyChangeListener_()); + } + + protected PropertyChangeListener buildHoursPropertyChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + DateTimeModelAdapter.this.hoursChanged(event); + } + @Override + public String toString() { + return "dateTime hours listener"; + } + }; + } + + protected PropertyChangeListener buildMinutesPropertyChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildMinutesPropertyChangeListener_()); + } + + protected PropertyChangeListener buildMinutesPropertyChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + DateTimeModelAdapter.this.minutesChanged(event); + } + @Override + public String toString() { + return "dateTime minutes listener"; + } + }; + } + + protected PropertyChangeListener buildSecondsPropertyChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildSecondsPropertyChangeListener_()); + } + + protected PropertyChangeListener buildSecondsPropertyChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + DateTimeModelAdapter.this.secondsChanged(event); + } + @Override + public String toString() { + return "dateTime seconds listener"; + } + }; + } + + protected SelectionListener buildDateTimeSelectionListener() { + return new SelectionListener() { + public void widgetSelected(SelectionEvent e) { + DateTimeModelAdapter.this.dateTimeSelected(e); + } + + public void widgetDefaultSelected(SelectionEvent e) { + } + + @Override + public String toString() { + return "dateTime selection listener"; + } + }; + } + + protected DisposeListener buildDateTimeDisposeListener() { + return new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + DateTimeModelAdapter.this.dateTimeDisposed(event); + } + @Override + public String toString() { + return "dateTime dispose listener"; + } + }; + } + + + // ********** model events ********** + + protected void hoursChanged(PropertyChangeEvent event) { + if (!this.locked) { + this.updateDateTimeHours((Integer) event.getNewValue()); + } + } + + protected void minutesChanged(PropertyChangeEvent event) { + if (!this.locked) { + this.updateDateTimeMinutes((Integer) event.getNewValue()); + } + } + + protected void secondsChanged(PropertyChangeEvent event) { + if (!this.locked) { + this.updateDateTimeSeconds((Integer) event.getNewValue()); + } + } + + // ********** dateTime events ********** + + protected void dateTimeSelected(SelectionEvent event) { + if (!this.locked) { + this.locked = true; + try { + //too bad they didn't split the event up + hoursSelected(); + minutesSelected(); + secondsSelected(); + } + finally { + this.locked = false; + } + } + } + + protected void hoursSelected() { + Integer hours = null; + if (this.dateTime.getHours() != 0) { + hours = Integer.valueOf(this.dateTime.getHours()); + } + this.hoursHolder.setValue(hours); + } + + protected void minutesSelected() { + Integer minutes = null; + if (this.dateTime.getMinutes() != 0) { + minutes = Integer.valueOf(this.dateTime.getMinutes()); + } + this.minutesHolder.setValue(minutes); + } + + protected void secondsSelected() { + Integer seconds = null; + if (this.dateTime.getSeconds() != 0) { + seconds = Integer.valueOf(this.dateTime.getSeconds()); + } + this.secondsHolder.setValue(seconds); + } + + protected void dateTimeDisposed(DisposeEvent event) { + // the dateTime is not yet "disposed" when we receive this event + // so we can still remove our listeners + this.dateTime.removeDisposeListener(this.dateTimeDisposeListener); + this.dateTime.removeSelectionListener(this.dateTimeSelectionListener); + this.hoursHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.hoursPropertyChangeListener); + this.minutesHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.minutesPropertyChangeListener); + this.secondsHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.secondsPropertyChangeListener); + } + + // ********** update ********** + + protected void updateDateTimeHours(Integer hours) { + if (this.dateTime.isDisposed()) { + return; + } + if (hours == null) { + hours = Integer.valueOf(0);//TODO defaultHours + } + this.locked = true; + try { + this.dateTime.setHours(hours.intValue()); + } + finally { + this.locked = false; + } + } + + protected void updateDateTimeMinutes(Integer minutes) { + if (this.dateTime.isDisposed()) { + return; + } + if (minutes == null) { + minutes = Integer.valueOf(0);//TODO defaultMinutes + } + this.locked = true; + try { + this.dateTime.setMinutes(minutes.intValue()); + } + finally { + this.locked = false; + } + } + + protected void updateDateTimeSeconds(Integer seconds) { + if (this.dateTime.isDisposed()) { + return; + } + if (seconds == null) { + seconds = Integer.valueOf(0);//TODO defaultSeconds + } + this.locked = true; + try { + this.dateTime.setSeconds(seconds.intValue()); + } + finally { + this.locked = false; + } + } + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.hoursHolder); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/SpinnerModelAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/SpinnerModelAdapter.java new file mode 100644 index 0000000000..58b03d2356 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/SpinnerModelAdapter.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.swt; + +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.widgets.Spinner; + +/** + * + */ +@SuppressWarnings("nls") +public class SpinnerModelAdapter { + + /** + * A value model on the underlying model list. + */ + protected final WritablePropertyValueModel<Integer> numberHolder; + + /** + * A listener that allows us to synchronize the spinner's contents with + * the model list. + */ + protected final PropertyChangeListener propertyChangeListener; + + /** + * The spinner we keep synchronized with the model string. + */ + protected final Spinner spinner; + + /** + * A listener that allows us to synchronize our selection number holder + * with the spinner's value. + */ + protected final ModifyListener spinnerModifyListener; + + /** + * A listener that allows us to stop listening to stuff when the spinner + * is disposed. + */ + protected final DisposeListener spinnerDisposeListener; + + /** + * The value shown when the number holder's value is <code>null</code>. + */ + protected final int defaultValue; + + /** + * This lock is used to prevent the listeners to be notified when the value + * changes from the spinner or from the holder. + */ + private boolean locked; + + // ********** static methods ********** + + /** + * Adapt the specified model list and selections to the specified spinner. + * Use the specified string converter to convert the model items to strings + * to be displayed in the spinner. + */ + public static SpinnerModelAdapter adapt( + WritablePropertyValueModel<Integer> numberHolder, + Spinner spinner, + int defaultValue) + { + return new SpinnerModelAdapter(numberHolder, spinner, defaultValue); + } + + + // ********** constructors ********** + + /** + * Constructor - the list holder, selections holder, list box, and + * string converter are required. + */ + protected SpinnerModelAdapter(WritablePropertyValueModel<Integer> numberHolder, + Spinner spinner, + int defaultValue) { + super(); + if ((numberHolder == null) || (spinner == null)) { + throw new NullPointerException(); + } + this.numberHolder = numberHolder; + this.spinner = spinner; + this.defaultValue = defaultValue; + + this.propertyChangeListener = this.buildPropertyChangeListener(); + this.numberHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.propertyChangeListener); + + this.spinnerModifyListener = this.buildSpinnerModifyListener(); + this.spinner.addModifyListener(this.spinnerModifyListener); + + this.spinnerDisposeListener = this.buildSpinnerDisposeListener(); + this.spinner.addDisposeListener(this.spinnerDisposeListener); + + this.updateSpinner(numberHolder.getValue()); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildPropertyChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildPropertyChangeListener_()); + } + + protected PropertyChangeListener buildPropertyChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + SpinnerModelAdapter.this.valueChanged(event); + } + @Override + public String toString() { + return "spinner listener"; + } + }; + } + + protected ModifyListener buildSpinnerModifyListener() { + return new ModifyListener() { + public void modifyText(ModifyEvent e) { + SpinnerModelAdapter.this.spinnerModified(e); + } + @Override + public String toString() { + return "spinner selection listener"; + } + }; + } + + protected DisposeListener buildSpinnerDisposeListener() { + return new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + SpinnerModelAdapter.this.spinnerDisposed(event); + } + @Override + public String toString() { + return "spinner dispose listener"; + } + }; + } + + + // ********** model events ********** + + protected void valueChanged(PropertyChangeEvent event) { + if (!this.locked) { + this.updateSpinner((Integer) event.getNewValue()); + } + } + + + // ********** spinner events ********** + + protected void spinnerModified(ModifyEvent event) { + if (!this.locked) { + this.locked = true; + try { + this.numberHolder.setValue(this.spinner.getSelection()); + } + finally { + this.locked = false; + } + } + } + + protected void spinnerDisposed(DisposeEvent event) { + // the spinner is not yet "disposed" when we receive this event + // so we can still remove our listeners + this.spinner.removeDisposeListener(this.spinnerDisposeListener); + this.spinner.removeModifyListener(this.spinnerModifyListener); + this.numberHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.propertyChangeListener); + } + + // ********** update ********** + + protected void updateSpinner(Integer value) { + if (this.spinner.isDisposed()) { + return; + } + // the model can be null, but the spinner cannot + if (value == null) { + value = defaultValue; + } + this.locked = true; + try { + this.spinner.setSelection(value); + } + finally { + this.locked = false; + } + } + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.numberHolder); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TableItemModelAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TableItemModelAdapter.java new file mode 100644 index 0000000000..d3f7089103 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TableItemModelAdapter.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.swt; + +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; + +/** + * This adapter can be used to keep a table item in synch with the properties of + * a model. + */ +@SuppressWarnings("nls") +public class TableItemModelAdapter { + + /** The table item we synchronize with the model. */ + protected final TableItem tableItem; + + /** + * A listener that allows us to stop listening to stuff when the button + * is disposed. + */ + protected final DisposeListener tableItemDisposeListener; + + /** + * Client-supplied adapter that provides with the various column settings and + * converts the objects in the LVM into an array of cell models. + */ + private ColumnAdapter<Object> columnAdapter; + + /** + * The value models used to listen to each property that are display by the + * table item. + */ + private WritablePropertyValueModel<?>[] valueHolders; + + /** + * The list of <code>PropertyChangeListener</code>s used to be notified when + * the properties of the model being display into a row change. + */ + private PropertyChangeListener[] propertyChangeListeners; + + /** + * The label used to format the objects into a string representation. + */ + private ITableLabelProvider labelProvider; + + // ********** static methods ********** + + /** + * Adapt the specified boolean to the specified button. + * If the boolean is null, the button's value will be "unselected". + */ + public static TableItemModelAdapter adapt(TableItem tableItem, ColumnAdapter<?> columnAdapter, ITableLabelProvider labelProvider) { + return new TableItemModelAdapter(tableItem, columnAdapter, labelProvider); + } + + + // ********** constructors ********** + + /** + * Constructor - the boolean holder and button are required. + */ + @SuppressWarnings("unchecked") + protected TableItemModelAdapter(TableItem tableItem, ColumnAdapter<?> columnAdapter, ITableLabelProvider labelProvider) { + super(); + if (tableItem == null || columnAdapter == null || labelProvider == null) { + throw new NullPointerException(); + } + this.tableItem = tableItem; + this.labelProvider = labelProvider; + this.columnAdapter = (ColumnAdapter<Object>) columnAdapter; + + this.tableItemDisposeListener = this.buildTableItemDisposeListener(); + this.tableItem.addDisposeListener(this.tableItemDisposeListener); + + this.valueHolders = this.columnAdapter.cellModels(tableItem.getData()); + this.propertyChangeListeners = this.buildPropertyChangeListeners(); + + for (int index = this.columnAdapter.columnCount(); --index >= 0; ) { + tableItemChanged(index, tableItem.getData(), false); + valueHolders[index].addPropertyChangeListener(PropertyValueModel.VALUE, propertyChangeListeners[index]); + } + } + + + // ********** initialization ********** + + private PropertyChangeListener[] buildPropertyChangeListeners() { + PropertyChangeListener[] listeners = new PropertyChangeListener[columnAdapter.columnCount()]; + for (int index = listeners.length; --index >= 0; ) { + listeners[index] = buildPropertyChangeListener(index); + } + return listeners; + } + + + protected PropertyChangeListener buildPropertyChangeListener(int index) { + return new SWTPropertyChangeListenerWrapper( + this.buildPropertyChangeListener_(index) + ); + } + + protected PropertyChangeListener buildPropertyChangeListener_(int index) { + return new TableItemPropertyChangeListener(index); + } + + protected DisposeListener buildTableItemDisposeListener() { + return new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + TableItemModelAdapter.this.tableItemDisposed(event); + } + @Override + public String toString() { + return "TableItem dispose listener"; + } + }; + } + + + // ********** behavior ********** + + protected void tableItemChanged(int index, Object subject, boolean revalidate) { + + if (!this.tableItem.isDisposed()) { + this.updateTableItemText(index, subject); + this.updateTableItemImage(index, subject); + + if (revalidate) { + this.layoutTable(); + } + } + } + + private void updateTableItemText(int index, Object subject) { + String text = this.labelProvider.getColumnText(subject, index); + if (text == null) { + text = ""; + } + this.tableItem.setText(index, text); + } + + private void updateTableItemImage(int index, Object subject) { + Image image = this.labelProvider.getColumnImage(subject, index); + this.tableItem.setImage(index, image); + } + + private void layoutTable() { + // Refresh the table in order to show the scrollbar if required + Composite container = this.tableItem.getParent().getParent(); + container.computeSize(SWT.DEFAULT, SWT.DEFAULT); + container.layout(); + } + + // ********** dispose ********** + + protected void tableItemDisposed(DisposeEvent event) { + // the button is not yet "disposed" when we receive this event + // so we can still remove our listeners + this.tableItem.removeDisposeListener(this.tableItemDisposeListener); + + for (int index = valueHolders.length; --index >= 0; ) { + valueHolders[index].removePropertyChangeListener(PropertyValueModel.VALUE, propertyChangeListeners[index]); + } + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + private class TableItemPropertyChangeListener implements PropertyChangeListener { + + private final int index; + + TableItemPropertyChangeListener(int index) { + super(); + this.index = index; + } + + public void propertyChanged(PropertyChangeEvent event) { + if (!tableItem.isDisposed()) { + Table table = tableItem.getParent(); + tableItemChanged(index, tableItem.getData(), table.getColumnCount() == 0); + } + } + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TableModelAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TableModelAdapter.java new file mode 100644 index 0000000000..cc3d52a671 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TableModelAdapter.java @@ -0,0 +1,746 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.swt; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EventListener; +import java.util.EventObject; +import java.util.Iterator; +import java.util.List; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jpt.common.ui.internal.listeners.SWTCollectionChangeListenerWrapper; +import org.eclipse.jpt.common.ui.internal.listeners.SWTListChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.ArrayTools; +import org.eclipse.jpt.utility.internal.CollectionTools; +import org.eclipse.jpt.utility.internal.ListenerList; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.internal.model.value.PropertyCollectionValueModelAdapter; +import org.eclipse.jpt.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.utility.model.event.ListAddEvent; +import org.eclipse.jpt.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.utility.model.event.ListClearEvent; +import org.eclipse.jpt.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritableCollectionValueModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; + +/** + * This adapter provides a more object-oriented interface to the items and + * selected items in a table. + * {@link #listHolder} contains the data of a single column in the table. + * {@link #selectedItemsHolder} contains the data of a single column in + * {@link #listHolder} that are selected in the table. + */ +// TODO bjv +@SuppressWarnings("nls") +public class TableModelAdapter<E> { + + // ********** model ********** + /** + * A value model on the underlying model list. + */ + protected final ListValueModel<E> listHolder; + + /** + * A listener that allows us to synchronize the table's contents with + * the model list. + */ + protected final ListChangeListener listChangeListener; + + /** + * A value model on the underlying model selections. + */ + protected final CollectionValueModel<E> selectedItemsHolder; + + /** + * A listener that allows us to synchronize the table's selection with + * the model selections. + */ + protected final CollectionChangeListener selectedItemsChangeListener; + + /** + * The table we keep synchronized with the model list. + */ + protected final Table table; + + /** + * A listener that allows us to synchronize our selection list holder + * with the table's selection. + */ + protected final SelectionListener tableSelectionListener; + + /** + * Clients that are interested in selection change events. + */ + protected final ListenerList<SelectionChangeListener<E>> selectionChangeListenerList; + + /** + * Clients that are interested in double click events. + */ + protected final ListenerList<DoubleClickListener<E>> doubleClickListenerList; + + /** + * A listener that allows us to stop listening to stuff when the table + * is disposed. + */ + protected final DisposeListener tableDisposeListener; + + /** + * This label provider is responsible to convert a property at a column index + * to a string value. + */ + protected final ITableLabelProvider labelProvider; + + /** + * The column adapter is responsible to return the count of columns and to + * create the value holders for all the properties. + */ + private ColumnAdapter<E> columnAdapter; + + /** + * Keeps track of the <code>TableItemModelAdapter</code>s that were created + * for each item of the list holder. + */ + private List<TableItemModelAdapter> tableItemModelAdapters; + + + // ********** static methods ********** + + /** + * Adapt the specified list model and selection to the specified table. + * Use the specified string converter to convert the model items to strings + * to be displayed in the table. + */ + public static <T> TableModelAdapter<T> adapt( + ListValueModel<T> listHolder, + PropertyValueModel<T> selectedItemHolder, + Table table, + ColumnAdapter<T> columnAdapter, + ITableLabelProvider labelProvider) { + + return new TableModelAdapter<T>( + listHolder, + new PropertyCollectionValueModelAdapter<T>(selectedItemHolder), + table, + columnAdapter, + labelProvider); + } + + /** + * Adapt the specified list model and selection to the specified table. + * The specified selection model will be kept in sync with the table. + * Use the specified string converter to convert the model items to strings + * to be displayed in the table. + */ + public static <T> TableModelAdapter<T> adapt( + ListValueModel<T> listHolder, + WritableCollectionValueModel<T> selectionModel, + Table table, + ColumnAdapter<T> columnAdapter, + ITableLabelProvider labelProvider) { + + TableModelAdapter adapter = + new TableModelAdapter<T>( + listHolder, + selectionModel, + table, + columnAdapter, + labelProvider); + adapter.addSelectionChangeListener(buildSyncListener(selectionModel)); + return adapter; + } + + private static <T> SelectionChangeListener buildSyncListener( + final WritableCollectionValueModel<T> selectionModel) { + + return new SelectionChangeListener() { + public void selectionChanged(SelectionChangeEvent event) { + selectionModel.setValues(CollectionTools.iterable(event.selection())); + } + }; + } + + + // ********** constructors ********** + + /** + * Constructor - the list holder, selections holder, table, and + * string converter are required. + */ + protected TableModelAdapter( + ListValueModel<E> listHolder, + CollectionValueModel<E> selectedItemsHolder, + Table table, + ColumnAdapter<E> columnAdapter, + ITableLabelProvider labelProvider) + { + super(); + if ((listHolder == null) || (selectedItemsHolder == null) || (table == null) || (labelProvider == null)) { + throw new NullPointerException(); + } + this.listHolder = listHolder; + this.selectedItemsHolder = selectedItemsHolder; + this.table = table; + this.columnAdapter = columnAdapter; + this.labelProvider = labelProvider; + this.tableItemModelAdapters = new ArrayList<TableItemModelAdapter>(columnAdapter.columnCount()); + + this.listChangeListener = this.buildListChangeListener(); + this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + + this.selectedItemsChangeListener = this.buildSelectedItemsChangeListener(); + this.selectedItemsHolder.addCollectionChangeListener(CollectionValueModel.VALUES, this.selectedItemsChangeListener); + + this.tableSelectionListener = this.buildTableSelectionListener(); + this.table.addSelectionListener(this.tableSelectionListener); + + this.selectionChangeListenerList = this.buildSelectionChangeListenerList(); + this.doubleClickListenerList = this.buildDoubleClickListenerList(); + + this.tableDisposeListener = this.buildTableDisposeListener(); + this.table.addDisposeListener(this.tableDisposeListener); + + this.synchronizeTable(); + } + + + // ********** initialization ********** + + protected ListChangeListener buildListChangeListener() { + return new SWTListChangeListenerWrapper(this.buildListChangeListener_()); + } + + protected ListChangeListener buildListChangeListener_() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + TableModelAdapter.this.listItemsAdded(event); + } + public void itemsRemoved(ListRemoveEvent event) { + TableModelAdapter.this.listItemsRemoved(event); + } + public void itemsMoved(ListMoveEvent event) { + TableModelAdapter.this.listItemsMoved(event); + } + public void itemsReplaced(ListReplaceEvent event) { + TableModelAdapter.this.listItemsReplaced(event); + } + public void listCleared(ListClearEvent event) { + TableModelAdapter.this.listCleared(event); + } + public void listChanged(ListChangeEvent event) { + TableModelAdapter.this.listChanged(event); + } + @Override + public String toString() { + return "TableModelAdapter list listener"; + } + }; + } + + protected CollectionChangeListener buildSelectedItemsChangeListener() { + return new SWTCollectionChangeListenerWrapper(this.buildSelectedItemsChangeListener_()); + } + + protected CollectionChangeListener buildSelectedItemsChangeListener_() { + return new CollectionChangeListener() { + public void itemsAdded(CollectionAddEvent event) { + TableModelAdapter.this.selectedItemsAdded(event); + } + public void itemsRemoved(CollectionRemoveEvent event) { + TableModelAdapter.this.selectedItemsRemoved(event); + } + public void collectionCleared(CollectionClearEvent event) { + TableModelAdapter.this.selectedItemsCleared(event); + } + public void collectionChanged(CollectionChangeEvent event) { + TableModelAdapter.this.selectedItemsChanged(event); + } + @Override + public String toString() { + return "TableModelAdapter selected items listener"; + } + }; + } + + protected SelectionListener buildTableSelectionListener() { + return new SelectionListener() { + public void widgetSelected(SelectionEvent event) { + TableModelAdapter.this.tableSelectionChanged(event); + } + public void widgetDefaultSelected(SelectionEvent event) { + TableModelAdapter.this.tableDoubleClicked(event); + } + @Override + public String toString() { + return "TableModelAdapter table selection listener"; + } + }; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + protected ListenerList<DoubleClickListener<E>> buildDoubleClickListenerList() { + return new ListenerList(DoubleClickListener.class); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + protected ListenerList<SelectionChangeListener<E>> buildSelectionChangeListenerList() { + return new ListenerList(SelectionChangeListener.class); + } + + protected DisposeListener buildTableDisposeListener() { + return new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + TableModelAdapter.this.tableDisposed(event); + } + @Override + public String toString() { + return "TableModelAdapter table dispose listener"; + } + }; + } + + protected void synchronizeTable() { + this.synchronizeTableColumns(); + this.synchronizeTableItems(); + this.synchronizeTableSelection(); + } + + + // ********** list ********** + + /** + * Creates the table colums. + */ + protected void synchronizeTableColumns() { + if (this.table.isDisposed()) { + return; + } + + int columnCount = this.columnAdapter.columnCount(); + + for (int index = 0; index < columnCount; index++) { + TableColumn tableColumn = new TableColumn(this.table, SWT.NULL, index); + tableColumn.setMoveable(false); + tableColumn.setResizable(true); + tableColumn.setWidth(100); + + String columnName = this.columnAdapter.columnName(index); + + if (columnName == null) { + columnName = ""; + } + + tableColumn.setText(columnName); + } + } + + /** + * Brute force synchronization of table with the model list. + */ + protected void synchronizeTableItems() { + if (this.table.isDisposed()) { + return; + } + + for (int index = this.table.getItemCount(); --index >= 0; ) { + this.table.remove(index); + this.tableItemModelAdapters.remove(index); + } + + int itemCount = this.listHolder.size(); + + for (int index = 0; index < itemCount; index++) { + + TableItem tableItem = new TableItem(this.table, SWT.NULL, index); + tableItem.setData(this.listHolder.get(index)); + + TableItemModelAdapter adapter = this.buildItemModel(tableItem); + this.tableItemModelAdapters.add(adapter); + } + } + + /** + * The model has changed - synchronize the table. + */ + protected void listItemsAdded(ListAddEvent event) { + + if (this.table.isDisposed()) { + return; + } + + int index = event.getIndex(); + + for (E item : this.getItems(event)) { + + TableItem tableItem = new TableItem(this.table, SWT.NULL, index); + tableItem.setData(item); + + TableItemModelAdapter adapter = this.buildItemModel(tableItem); + this.tableItemModelAdapters.add(index++, adapter); + } + } + + /** + * The model has changed - synchronize the table. + */ + protected void listItemsRemoved(ListRemoveEvent event) { + + if (this.table.isDisposed()) { + return; + } + + this.table.remove(event.getIndex(), event.getIndex() + event.getItemsSize() - 1); + + for (int index = event.getIndex() + event.getItemsSize(); --index >= event.getIndex(); ) { + this.tableItemModelAdapters.remove(index); + } + } + + /** + * The model has changed - synchronize the table. + */ + protected void listItemsMoved(ListMoveEvent event) { + + if (this.table.isDisposed()) { + return; + } + + int length = event.getLength(); + int sourceIndex = event.getSourceIndex(); + int targetIndex = event.getTargetIndex(); + int lowStartIndex = Math.min(targetIndex, sourceIndex); + int hiStartIndex = Math.max(targetIndex, sourceIndex); + + Object[] items = new Object[hiStartIndex - lowStartIndex + length]; + int itemsIndex = items.length; + + // Remove the TableItems wrapping the moved items + for (int index = hiStartIndex + length; --index >= lowStartIndex; ) { + + TableItemModelAdapter tableItemModel = this.tableItemModelAdapters.get(index); + items[--itemsIndex] = tableItemModel.tableItem.getData(); + + // Remove the TableItem, which will also dispose TableItemModelAdapter + this.table.remove(index); + } + + // Move the items so they can retrieved in the right order when + // re-creating the TableItems + ArrayTools.move( + items, + targetIndex - lowStartIndex, + sourceIndex - lowStartIndex, + length + ); + + itemsIndex = 0; + + // Add TableItems for the moved items + for (int index = lowStartIndex; index <= hiStartIndex + length - 1; index++) { + + // Create the new TableItem + TableItem tableItem = new TableItem(this.table, SWT.NULL, index); + tableItem.setData(items[itemsIndex++]); + + // Adapt it with a model adapter + TableItemModelAdapter adapter = this.buildItemModel(tableItem); + this.tableItemModelAdapters.set(index, adapter); + } + } + + + private TableItemModelAdapter buildItemModel(TableItem tableItem) { + return TableItemModelAdapter.adapt( + tableItem, + this.columnAdapter, + this.labelProvider + ); + } + + /** + * The model has changed - synchronize the table. + */ + protected void listItemsReplaced(ListReplaceEvent event) { + if (this.table.isDisposed()) { + return; + } + + int rowIndex = event.getIndex(); + + for (E item : this.getNewItems(event)) { + TableItem tableItem = this.table.getItem(rowIndex); + tableItem.setData(item); + + TableItemModelAdapter adapter = this.tableItemModelAdapters.get(rowIndex); + + int columnCount = this.columnAdapter.columnCount(); + boolean revalidate = (columnCount == 1); + + for (int columnIndex = columnCount; --columnIndex >= 0; ) { + adapter.tableItemChanged(columnIndex, tableItem.getData(), revalidate); + } + + rowIndex++; + } + } + + /** + * The model has changed - synchronize the table. + */ + protected void listCleared(@SuppressWarnings("unused") ListClearEvent event) { + if (this.table.isDisposed()) { + return; + } + this.table.removeAll(); + } + + /** + * The model has changed - synchronize the table. + */ + protected void listChanged(@SuppressWarnings("unused") ListChangeEvent event) { + this.synchronizeTableItems(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable<E> getItems(ListAddEvent event) { + return (Iterable<E>) event.getItems(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable<E> getNewItems(ListReplaceEvent event) { + return (Iterable<E>) event.getNewItems(); + } + + + // ********** selected items ********** + + protected int indexOf(E item) { + int len = this.listHolder.size(); + for (int i = 0; i < len; i++) { + if (this.listHolder.get(i) == item) { + return i; + } + } + return -1; + } + + protected void synchronizeTableSelection() { + if (this.table.isDisposed()) { + return; + } + int[] indices = new int[this.selectedItemsHolder.size()]; + int i = 0; + for (E selectedItemHolder : this.selectedItemsHolder) { + indices[i++] = this.indexOf(selectedItemHolder); + } + this.table.deselectAll(); + this.table.select(indices); + } + + protected void selectedItemsAdded(CollectionAddEvent event) { + if (this.table.isDisposed()) { + return; + } + this.table.select(this.getIndices(event.getItemsSize(), this.getItems(event))); + } + + protected void selectedItemsRemoved(CollectionRemoveEvent event) { + if (this.table.isDisposed()) { + return; + } + this.table.deselect(this.getIndices(event.getItemsSize(), this.getItems(event))); + } + + protected int[] getIndices(int itemsSize, Iterable<E> items) { + int[] indices = new int[itemsSize]; + int i = 0; + for (E item : items) { + indices[i++] = this.indexOf(item); + } + return indices; + } + + protected void selectedItemsCleared(@SuppressWarnings("unused") CollectionClearEvent event) { + if (this.table.isDisposed()) { + return; + } + this.table.deselectAll(); + } + + protected void selectedItemsChanged(@SuppressWarnings("unused") CollectionChangeEvent event) { + this.synchronizeTableSelection(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable<E> getItems(CollectionAddEvent event) { + return (Iterable<E>) event.getItems(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + protected Iterable<E> getItems(CollectionRemoveEvent event) { + return (Iterable<E>) event.getItems(); + } + + + // ********** list box events ********** + + protected void tableSelectionChanged(@SuppressWarnings("unused") SelectionEvent event) { + if (this.selectionChangeListenerList.size() > 0) { + SelectionChangeEvent<E> scEvent = new SelectionChangeEvent<E>(this, this.selectedItems()); + for (SelectionChangeListener<E> selectionChangeListener : this.selectionChangeListenerList.getListeners()) { + selectionChangeListener.selectionChanged(scEvent); + } + } + } + + protected Collection<E> selectedItems() { + if (this.table.isDisposed()) { + return Collections.emptySet(); + } + ArrayList<E> selectedItems = new ArrayList<E>(this.table.getSelectionCount()); + for (int selectionIndex : this.table.getSelectionIndices()) { + selectedItems.add(this.listHolder.get(selectionIndex)); + } + return selectedItems; + } + + protected void tableDoubleClicked(@SuppressWarnings("unused") SelectionEvent event) { + if (this.table.isDisposed()) { + return; + } + if (this.doubleClickListenerList.size() > 0) { + // there should be only a single item selected during a double-click(?) + E selection = this.listHolder.get(this.table.getSelectionIndex()); + DoubleClickEvent<E> dcEvent = new DoubleClickEvent<E>(this, selection); + for (DoubleClickListener<E> doubleClickListener : this.doubleClickListenerList.getListeners()) { + doubleClickListener.doubleClick(dcEvent); + } + } + } + + + // ********** dispose ********** + + protected void tableDisposed(@SuppressWarnings("unused") DisposeEvent event) { + // the table is not yet "disposed" when we receive this event + // so we can still remove our listeners + this.table.removeDisposeListener(this.tableDisposeListener); + this.table.removeSelectionListener(this.tableSelectionListener); + this.selectedItemsHolder.removeCollectionChangeListener(CollectionValueModel.VALUES, this.selectedItemsChangeListener); + this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.listHolder); + } + + + // ********** double click support ********** + + public void addDoubleClickListener(DoubleClickListener<E> listener) { + this.doubleClickListenerList.add(listener); + } + + public void removeDoubleClickListener(DoubleClickListener<E> listener) { + this.doubleClickListenerList.remove(listener); + } + + public interface DoubleClickListener<E> extends EventListener { + void doubleClick(DoubleClickEvent<E> event); + } + + public static class DoubleClickEvent<E> extends EventObject { + private final E selection; + private static final long serialVersionUID = 1L; + + protected DoubleClickEvent(TableModelAdapter<E> source, E selection) { + super(source); + if (selection == null) { + throw new NullPointerException(); + } + this.selection = selection; + } + + @Override + @SuppressWarnings("unchecked") + public TableModelAdapter<E> getSource() { + return (TableModelAdapter<E>) super.getSource(); + } + + public E selection() { + return this.selection; + } + + } + + + // ********** selection support ********** + + public void addSelectionChangeListener(SelectionChangeListener<E> listener) { + this.selectionChangeListenerList.add(listener); + } + + public void removeSelectionChangeListener(SelectionChangeListener<E> listener) { + this.selectionChangeListenerList.remove(listener); + } + + public interface SelectionChangeListener<E> extends EventListener { + void selectionChanged(SelectionChangeEvent<E> event); + } + + public static class SelectionChangeEvent<E> extends EventObject { + private final Collection<E> selection; + private static final long serialVersionUID = 1L; + + protected SelectionChangeEvent(TableModelAdapter<E> source, Collection<E> selection) { + super(source); + if (selection == null) { + throw new NullPointerException(); + } + this.selection = selection; + } + + @Override + @SuppressWarnings("unchecked") + public TableModelAdapter<E> getSource() { + return (TableModelAdapter<E>) super.getSource(); + } + + public Iterator<E> selection() { + return this.selection.iterator(); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TriStateCheckBoxModelAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TriStateCheckBoxModelAdapter.java new file mode 100644 index 0000000000..c04630b425 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TriStateCheckBoxModelAdapter.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.swt; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.ui.internal.widgets.TriStateCheckBox; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; + +/** + * This adapter can be used to keep a tri-state check box in synch with + * a model Boolean where the value can be <code>null</code>. + */ +@SuppressWarnings("nls") +public class TriStateCheckBoxModelAdapter { + + /** A value model on the underlying model boolean. */ + protected final WritablePropertyValueModel<Boolean> booleanHolder; + + /** + * A listener that allows us to synchronize the button's selection state with + * the model boolean. + */ + protected final PropertyChangeListener booleanChangeListener; + + /** The check box/toggle button we synchronize with the model boolean. */ + protected final TriStateCheckBox button; + + /** + * A listener that allows us to synchronize the model boolean with + * the button's selection state. + */ + protected final SelectionListener buttonSelectionListener; + + /** + * A listener that allows us to stop listening to stuff when the button + * is disposed. + */ + protected final DisposeListener buttonDisposeListener; + + + // ********** static methods ********** + + /** + * Adapt the specified boolean to the specified button. + * If the boolean is null, the button's value will be "partially checked" + * (i.e. the button will be checked but grayed out). + */ + public static TriStateCheckBoxModelAdapter adapt(WritablePropertyValueModel<Boolean> booleanHolder, TriStateCheckBox button) { + return new TriStateCheckBoxModelAdapter(booleanHolder, button); + } + + + // ********** constructors ********** + + /** + * Constructor - the boolean holder and button are required. + */ + protected TriStateCheckBoxModelAdapter(WritablePropertyValueModel<Boolean> booleanHolder, TriStateCheckBox button) { + super(); + + Assert.isNotNull(booleanHolder, "The boolean holder cannot be null"); + Assert.isNotNull(button, "The check box cannot be null"); + + this.booleanHolder = booleanHolder; + this.button = button; + + this.booleanChangeListener = this.buildBooleanChangeListener(); + this.booleanHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.booleanChangeListener); + + this.buttonDisposeListener = this.buildButtonDisposeListener(); + this.button.addDisposeListener(this.buttonDisposeListener); + + this.buttonSelectionListener = this.buildButtonSelectionListener(); + this.button.addSelectionListener(this.buttonSelectionListener); + + this.setButtonSelection(this.booleanHolder.getValue()); + } + + + // ********** initialization ********** + + protected PropertyChangeListener buildBooleanChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildBooleanChangeListener_()); + } + + protected PropertyChangeListener buildBooleanChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + TriStateCheckBoxModelAdapter.this.booleanChanged(event); + } + @Override + public String toString() { + return "tri-state boolean listener"; + } + }; + } + + protected SelectionListener buildButtonSelectionListener() { + return new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + TriStateCheckBoxModelAdapter.this.buttonSelected(event); + } + @Override + public String toString() { + return "tri-state button selection listener"; + } + }; + } + + protected DisposeListener buildButtonDisposeListener() { + return new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + TriStateCheckBoxModelAdapter.this.buttonDisposed(event); + } + @Override + public String toString() { + return "tri-state button dispose listener"; + } + }; + } + + + // ********** behavior ********** + + /** + * The model has changed - synchronize the button. + * If the new model value is null, use the adapter's default value + * (which is typically false). + */ + protected void booleanChanged(PropertyChangeEvent event) { + this.setButtonSelection((Boolean) event.getNewValue()); + } + + protected void setButtonSelection(Boolean selection) { + if (this.button.isDisposed()) { + return; + } + this.button.setSelection(selection); + } + + /** + * The button has been "selected" - synchronize the model. + */ + protected void buttonSelected(SelectionEvent event) { + if (this.button.isDisposed()) { + return; + } + this.booleanHolder.setValue(button.getSelection()); + } + + + // ********** dispose ********** + + protected void buttonDisposed(DisposeEvent event) { + // the button is not yet "disposed" when we receive this event + // so we can still remove our listeners + this.button.removeSelectionListener(this.buttonSelectionListener); + this.button.removeDisposeListener(this.buttonDisposeListener); + this.booleanHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.booleanChangeListener); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.booleanHolder); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/ControlAligner.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/ControlAligner.java new file mode 100644 index 0000000000..97f6c1a1fb --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/ControlAligner.java @@ -0,0 +1,913 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; + +/** + * This class is responsible to set a preferred width on the registered widgets + * (either <code>Control</code> or <code>ControlAligner</code>) based on the + * widest widget. + * <p> + * Important: The layout data has to be a <code>GridData</code>. If none is set, + * then a new <code>GridData</code> is automatically created. + * <p> + * Here an example of the result if this aligner is used to align controls + * within either one or two group boxes, the controls added are the labels in + * this case. It is also possible to align controls on the right side of the + * main component, a spacer can be used for extra space. + * <p> + * Here's an example: + * <pre> + * - Group Box 1 -------------------------------------------------------------- + * | -------------------------------------- ------------- | + * | Name: | I | | Browse... | | + * | -------------------------------------- ------------- | + * | --------- | + * | Preallocation Size: | |I| | + * | --------- | + * | -------------------------------------- | + * | Descriptor: | |v| | + * | -------------------------------------- | + * ---------------------------------------------------------------------------- + * + * - Group Box 2 -------------------------------------------------------------- + * | -------------------------------------- | + * | Mapping Type: | |V| | + * | -------------------------------------- | + * | -------------------------------------- | + * | Check in Script: | I | | + * | -------------------------------------- | + * ----------------------------------------------------------------------------</pre> + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public final class ControlAligner +{ + /** + * Flag used to prevent a validation so it can be done after an operation + * completed. + */ + private boolean autoValidate; + + /** + * The utility class used to support bound properties. + */ + private Collection<Listener> changeSupport; + + /** + * The listener added to each of the controls that listens only to a text + * change. + */ + private Listener listener; + + /** + * Prevents infinite recursion when recalculating the preferred width. + * This happens in an hierarchy of <code>ControlAligner</code>s. The lock + * has to be placed here and not in the {@link ControlAlignerWrapper}. + */ + private boolean locked; + + /** + * The length of the widest control. If the length was not calculated, then + * this value is 0. + */ + private int maximumWidth; + + /** + * The collection of {@link Wrapper}s encapsulating either <code>Control</code>s + * or {@link ControlAligner}s. + */ + private Collection<Wrapper> wrappers; + + /** + * A null-<code>Point</code> object used to clear the preferred size. + */ + private static final Point DEFAULT_SIZE = new Point(SWT.DEFAULT, SWT.DEFAULT); + + /** + * The types of events to listen in order to properly adjust the size of all + * the widgets. + */ + private static final int[] EVENT_TYPES = { + SWT.Dispose, + SWT.Hide, + SWT.Resize, + SWT.Show + }; + + /** + * Creates a new <code>ControlAligner</code>. + */ + public ControlAligner() { + super(); + initialize(); + } + + /** + * Creates a new <code>ControlAligner</code>. + * + * @param controls The collection of <code>Control</code>s + */ + public ControlAligner(Collection<? extends Control> controls) { + this(); + addAllControls(controls); + } + + /** + * Adds the given control. Its width will be used along with the width of all + * the other registered controls in order to get the greater witdh and use + * it as the width for all the controls. + * + * @param control The <code>Control</code> to be added + */ + public void add(Control control) { + + Assert.isNotNull(control, "Can't add null to this ControlAligner"); + + Wrapper wrapper = buildWrapper(control); + wrapper.addListener(listener); + wrappers.add(wrapper); + + revalidate(false); + } + + /** + * Adds the given control. Its width will be used along with the width of all + * the other registered controls in order to get the greater witdh and use + * it as the width for all the controls. + * + * @param controlAligner The <code>ControlAligner</code> to be added + * @exception IllegalArgumentException Can't add the <code>ControlAligner</code> + * to itself + */ + public void add(ControlAligner controlAligner) { + + Assert.isNotNull(controlAligner, "Can't add null to this ControlAligner"); + Assert.isLegal(controlAligner != this, "Can't add the ControlAligner to itself"); + + Wrapper wrapper = buildWrapper(controlAligner); + wrapper.addListener(listener); + wrappers.add(wrapper); + + if (!controlAligner.wrappers.isEmpty()) { + revalidate(false); + } + } + + /** + * Adds the items contained in the given collection into this + * <code>ControlAligner</code>. The preferred width of each item will be + * used along with the width of all the other items in order to get the + * widest control and use its width as the width for all the controls. + * + * @param aligners The collection of <code>ControlAligner</code>s + */ + public void addAllControlAligners(Collection<ControlAligner> aligners) { + + // Deactivate the auto validation while adding all the Controls and/or + // ControlAligners in order to improve performance + boolean oldAutoValidate = autoValidate; + autoValidate = false; + + for (ControlAligner aligner : aligners) { + add(aligner); + } + + autoValidate = oldAutoValidate; + revalidate(false); + } + + /** + * Adds the items contained in the given collection into this + * <code>ControlAligner</code>. The preferred width of each item will be + * used along with the width of all the other items in order to get the + * widest control and use its width as the width for all the controls. + * + * @param controls The collection of <code>Control</code>s + */ + public void addAllControls(Collection<? extends Control> controls) { + + // Deactivate the auto validation while adding all the Controls and/or + // ControlAligners in order to improve performance + boolean oldAutoValidate = autoValidate; + autoValidate = false; + + for (Control control : controls) { + add(control); + } + + autoValidate = oldAutoValidate; + revalidate(false); + } + + /** + * Adds the given <code>ControListener</code>. + * + * @param listener The <code>Listener</code> to be added + */ + private void addListener(Listener listener) { + + if (changeSupport == null) { + changeSupport = new ArrayList<Listener>(); + } + + changeSupport.add(listener); + } + + /** + * Creates a new <code>Wrapper</code> that encapsulates the given source. + * + * @param control The control to be wrapped + * @return A new {@link Wrapper} + */ + private Wrapper buildWrapper(Control control) { + return new ControlWrapper(control); + } + + /** + * Creates a new <code>Wrapper</code> that encapsulates the given source. + * + * @param ControlAligner The <code>ControlAligner</code> to be wrapped + * @return A new {@link ControlAlignerWrapper} + */ + private Wrapper buildWrapper(ControlAligner ControlAligner) { + return new ControlAlignerWrapper(ControlAligner); + } + + /** + * Calculates the width taken by the widgets and returns the maximum width. + * + * @param recalculateSize <code>true</code> to recalculate the preferred size + * of all the wrappers contained within them rather than using the cached + * size; <code>false</code> to use the cached size + */ + private int calculateWidth(boolean recalculateSize) { + + int width = 0; + + for (Wrapper wrapper : wrappers) { + Point size = wrapper.cachedSize(); + + // The size has not been calculated yet + if (recalculateSize || (size.x == 0)) { + size = wrapper.calculateSize(); + } + + // Only keep the greatest width + width = Math.max(size.x, width); + } + + return width; + } + + /** + * Reports a bound property change. + * + * @param oldValue the old value of the property (as an int) + * @param newValue the new value of the property (as an int) + */ + private void controlResized(int oldValue, int newValue) { + + if ((changeSupport != null) && (oldValue != newValue)) { + Event event = new Event(); + event.widget = SWTUtil.getShell(); + event.data = this; + + for (Listener listener : changeSupport) { + listener.handleEvent(event); + } + } + } + + /** + * Disposes this <code>ControlAligner</code>, this can improve the speed of + * disposing a pane. When a pane is disposed, this aligner doesn't need to + * revalidate its size upon dispose of its widgets. + */ + public void dispose() { + + for (Iterator<Wrapper> iter = wrappers.iterator(); iter.hasNext(); ) { + Wrapper wrapper = iter.next(); + wrapper.removeListener(listener); + iter.remove(); + } + + this.wrappers.clear(); + } + + /** + * Returns the length of the widest control. If the length was not + * calculated, then this value is 0. + * + * @return The width of the widest control or 0 if the length has not been + * calculated yet + */ + public int getMaximumWidth() { + return maximumWidth; + } + + /** + * Initializes this <code>ControlAligner</code>. + */ + private void initialize() { + + this.autoValidate = true; + this.maximumWidth = 0; + this.listener = new ListenerHandler(); + this.wrappers = new ArrayList<Wrapper>(); + } + + /** + * Invalidates the size of the given object. + * + * @param source The source object to be invalidated + */ + private void invalidate(Object source) { + + Wrapper wrapper = retrieveWrapper(source); + + if (!wrapper.locked()) { + Point size = wrapper.cachedSize(); + size.x = size.y = 0; + wrapper.setSize(DEFAULT_SIZE); + } + } + + /** + * Updates the maximum length based on the widest control. This methods + * does not update the width of the controls. + * + * @param recalculateSize <code>true</code> to recalculate the preferred size + * of all the wrappers contained within them rather than using the cached + * size; <code>false</code> to use the cached size + */ + private void recalculateWidth(boolean recalculateSize) { + + int width = calculateWidth(recalculateSize); + + try { + locked = true; + setMaximumWidth(width); + } + finally { + locked = false; + } + } + + /** + * Removes the given control. Its preferred width will not be used when + * calculating the preferred width. + * + * @param control The control to be removed + * @exception AssertionFailedException If the given <code>Control</code> is + * <code>null</code> + */ + public void remove(Control control) { + + Assert.isNotNull(control, "The Control to remove cannot be null"); + + Wrapper wrapper = retrieveWrapper(control); + wrapper.removeListener(listener); + wrappers.remove(wrapper); + + revalidate(true); + } + + /** + * Removes the given <code>ControlAligner</code>. Its preferred width + * will not be used when calculating the preferred witdh. + * + * @param controlAligner The <code>ControlAligner</code> to be removed + * @exception AssertionFailedException If the given <code>ControlAligner</code> + * is <code>null</code> + */ + public void remove(ControlAligner controlAligner) { + + Assert.isNotNull(controlAligner, "The ControlAligner to remove cannot be null"); + + Wrapper wrapper = retrieveWrapper(controlAligner); + wrapper.removeListener(listener); + wrappers.remove(wrapper); + + revalidate(true); + } + + /** + * Removes the given <code>Listener</code>. + * + * @param listener The <code>Listener</code> to be removed + */ + private void removeListener(Listener listener) { + + changeSupport.remove(listener); + + if (changeSupport.isEmpty()) { + changeSupport = null; + } + } + + /** + * Retrieves the <code>Wrapper</code> that is encapsulating the given object. + * + * @param source Either a <code>Control</code> or a <code>ControlAligner</code> + * @return Its <code>Wrapper</code> + */ + private Wrapper retrieveWrapper(Object source) { + + for (Wrapper wrapper : wrappers) { + if (wrapper.source() == source) { + return wrapper; + } + } + + throw new IllegalArgumentException("Can't retrieve the Wrapper for " + source); + } + + /** + * If the count of control is greater than one and {@link #isAutoValidate()} + * returns <code>true</code>, then the size of all the registered + * <code>Control</code>s will be udpated. + * + * @param recalculateSize <code>true</code> to recalculate the preferred size + * of all the wrappers contained within them rather than using the cached + * size; <code>false</code> to use the cached size + */ + private void revalidate(boolean recalculateSize) { + + if (autoValidate) { + recalculateWidth(recalculateSize); + updateWrapperSize(recalculateSize); + } + } + + /** + * Bases on the information contained in the given <code>Event</code>, + * resize the controls. + * + * @param event The <code>Event</code> sent by the UI thread when the state + * of a widget changed + */ + private void revalidate(Event event) { + + // We don't need to revalidate during a revalidation process + if (locked) { + return; + } + + Object source; + + if (event.widget != SWTUtil.getShell()) { + source = event.widget; + Control control = (Control) source; + + // When a dialog is opened, we need to actually force a layout of + // the controls, this is required because the control is actually + // not visible when the preferred width is caculated + if (control == control.getShell()) { + if (event.type == SWT.Dispose) { + return; + } + + source = null; + } + } + else { + source = event.data; + } + + // Either remove the ControlWrapper if the widget was disposed or + // invalidate the widget in order to recalculate the preferred size + if (source != null) { + if (event.type == SWT.Dispose) { + Wrapper wrapper = retrieveWrapper(source); + wrappers.remove(wrapper); + } + else { + invalidate(source); + } + } + + // Now revalidate all the Controls and ControlAligners + revalidate(true); + } + + /** + * Sets the length of the widest control. If the length was not calulcated, + * then this value is 0. + * + * @param maximumWidth The width of the widest control + */ + private void setMaximumWidth(int maximumWidth) { + + int oldMaximumWidth = this.maximumWidth; + this.maximumWidth = maximumWidth; + controlResized(oldMaximumWidth, maximumWidth); + } + + /** + * Returns a string representation of this <code>ControlAligner</code>. + * + * @return Information about this object + */ + @Override + public String toString() { + + StringBuffer sb = new StringBuffer(); + sb.append("maximumWidth="); + sb.append(maximumWidth); + sb.append(", wrappers="); + sb.append(wrappers); + return StringTools.buildToStringFor(this, sb); + } + + /** + * Updates the size of every <code>Wrapper</code> based on the maximum width. + * + * @param forceRevalidate <code>true</code> to revalidate the wrapper's size + * even though its current size might be the same as the maximum width; + * <code>false</code> to only revalidate the wrappers with a different width + */ + private void updateWrapperSize(boolean forceRevalidate) { + + for (Wrapper wrapper : wrappers) { + Point cachedSize = wrapper.cachedSize(); + + // No need to change the size of the wrapper since it's always using + // the maximum width + if (forceRevalidate || (cachedSize.x != maximumWidth)) { + Point size = new Point(maximumWidth, cachedSize.y); + wrapper.setSize(size); + } + } + } + + /** + * This <code>Wrapper</code> encapsulates a {@link ControlAligner}. + */ + private class ControlAlignerWrapper implements Wrapper { + /** + * The cached size, which is {@link ControlAligner#maximumWidth}. + */ + private final Point cachedSize; + + /** + * The <code>ControlAligner</code> encapsulated by this + * <code>Wrapper</code>. + */ + private final ControlAligner controlAligner; + + /** + * Creates a new <code>ControlAlignerWrapper</code> that encapsulates + * the given <code>ControlAligner</code>. + * + * @param controlAligner The <code>ControlAligner</code> to be + * encapsulated by this <code>Wrapper</code> + */ + private ControlAlignerWrapper(ControlAligner controlAligner) { + super(); + this.controlAligner = controlAligner; + this.cachedSize = new Point(controlAligner.maximumWidth, 0); + } + + /* + * (non-Javadoc) + */ + public void addListener(Listener listener) { + controlAligner.addListener(listener); + } + + /* + * (non-Javadoc) + */ + public Point cachedSize() { + cachedSize.x = controlAligner.maximumWidth; + return cachedSize; + } + + /* + * (non-Javadoc) + */ + public Point calculateSize() { + + Point size = new Point(controlAligner.calculateWidth(false), 0); + + if (size.x != SWT.DEFAULT) { + cachedSize.x = size.x; + } + else { + cachedSize.x = 0; + } + + if (size.y != SWT.DEFAULT) { + cachedSize.y = size.y; + } + else { + cachedSize.y = 0; + } + + return size; + } + + /* + * (non-Javadoc) + */ + public boolean locked() { + return controlAligner.locked; + } + + /* + * (non-Javadoc) + */ + public void removeListener(Listener listener) { + controlAligner.removeListener(listener); + } + + /* + * (non-Javadoc) + */ + public void setSize(Point size) { + + if (size == DEFAULT_SIZE) { + controlAligner.maximumWidth = 0; + } + else if (controlAligner.maximumWidth != size.x) { + controlAligner.maximumWidth = size.x; + controlAligner.updateWrapperSize(true); + } + } + + /* + * (non-Javadoc) + */ + public Object source() { + return controlAligner; + } + + /* + * (non-Javadoc) + */ + @Override + public String toString() { + + StringBuffer sb = new StringBuffer(); + sb.append("Cached size="); + sb.append(cachedSize); + sb.append(", ControlAligner="); + sb.append(controlAligner); + return StringTools.buildToStringFor(this, sb); + } + } + + /** + * This <code>Wrapper</code> encapsulates a {@link Control}. + */ + private class ControlWrapper implements Wrapper { + /** + * The cached size, which is control's size. + */ + private Point cachedSize; + + /** + * The control to be encapsulated by this <code>Wrapper</code>. + */ + private final Control control; + + /** + * Creates a new <code>controlWrapper</code> that encapsulates the given + * control. + * + * @param control The control to be encapsulated by this <code>Wrapper</code> + */ + private ControlWrapper(Control control) { + super(); + + this.control = control; + this.cachedSize = new Point(0, 0); + } + + /* + * (non-Javadoc) + */ + public void addListener(Listener listener) { + + for (int eventType : EVENT_TYPES) { + control.addListener(eventType, listener); + } + } + + /* + * (non-Javadoc) + */ + public Point cachedSize() { + return cachedSize; + } + + /* + * (non-Javadoc) + */ + public Point calculateSize() { + + cachedSize = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + + // Update right away the control's GridData + GridData gridData = (GridData) control.getLayoutData(); + + if (gridData == null) { + gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + control.setLayoutData(gridData); + } + + gridData.widthHint = cachedSize.x; + gridData.heightHint = cachedSize.y; + + // Make sure the size is not -1 + if (cachedSize.x == SWT.DEFAULT) { + cachedSize.x = 0; + } + + if (cachedSize.y == SWT.DEFAULT) { + cachedSize.y = 0; + } + + return cachedSize; + } + + /* + * (non-Javadoc) + */ + public boolean locked() { + return false; + } + + /* + * (non-Javadoc) + */ + public void removeListener(Listener listener) { + + for (int eventType : EVENT_TYPES) { + control.removeListener(eventType, listener); + } + } + + /* + * (non-Javadoc) + */ + public void setSize(Point size) { + + if (control.isDisposed()) { + return; + } + + // Update the GridData with the new size + GridData gridData = (GridData) control.getLayoutData(); + + if (gridData == null) { + gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + control.setLayoutData(gridData); + } + + gridData.widthHint = size.x; + gridData.heightHint = size.y; + + // Force the control to be resized, and tell its parent to layout + // its widgets + if (size.x > 0) { +// locked = true; +// try { +//// control.getParent().layout(new Control[] { control }); +// control.getParent().layout(true); +// } +// finally { +// locked = false; +// } + Rectangle bounds = control.getBounds(); + + // Only update the control's width if it's + // different from the current size + if (bounds.width != size.x) { + locked = true; + + try { +// control.setBounds(bounds.x, bounds.y, size.x, size.y); + control.getParent().layout(true); + } + finally + { + locked = false; + } + } + } + } + + /* + * (non-Javadoc) + */ + public Control source() { + return control; + } + + /* + * (non-Javadoc) + */ + @Override + public String toString() { + + StringBuffer sb = new StringBuffer(); + sb.append("Cached size="); + sb.append(cachedSize); + sb.append(", Control="); + sb.append(control); + return StringTools.buildToStringFor(this, sb); + } + } + + /** + * The listener added to each of the control that is notified in order to + * revalidate the preferred size. + */ + private class ListenerHandler implements Listener { + public void handleEvent(Event event) { + ControlAligner.this.revalidate(event); + } + } + + /** + * This <code>Wrapper</code> helps to encapsulate heterogeneous objects and + * apply the same behavior on them. + */ + private interface Wrapper { + /** + * Adds the given <code>Listener</code> to wrapped object in order to + * receive notification when its property changed. + * + * @param listener The <code>Listener</code> to be added + */ + void addListener(Listener listener); + + /** + * Returns the cached size of the encapsulated source. + * + * @return A non-<code>null</code> <code>Point</code> where the x is the + * width and the y is the height of the widget + */ + Point cachedSize(); + + /** + * Calculates the preferred size the wrapped object would take by itself. + * + * @return The calculated size + */ + Point calculateSize(); + + /** + * Prevents infinite recursion when recalculating the preferred width. + * This happens in an hierarchy of <code>ControlAligner</code>s. + * + * @return <code>true</code> to prevent this <code>Wrapper</code> from + * being invalidated; otherwise <code>false</code> + */ + boolean locked(); + + /** + * Removes the given <code>Listener</code>. + * + * @param listener The <code>Listener</code> to be removed + */ + void removeListener(Listener listener); + + /** + * Sets the size on the encapsulated source. + * + * @param size The new size + */ + void setSize(Point size); + + /** + * Returns the encapsulated object. + * + * @return The object that is been wrapped + */ + Object source(); + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/ControlSwitcher.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/ControlSwitcher.java new file mode 100644 index 0000000000..e67ded57ed --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/ControlSwitcher.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.util; + +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.Transformer; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.part.PageBook; + +/** + * This controller is responsible to switch the active page based on a value. A + * <code>Transformer</code> is used to transformed that value into a + * <code>Control</code>. + * + * @version 2.3 + * @since 2.0 + */ +public final class ControlSwitcher +{ + /** + * The widget that is used to show the active <code>Control</code>. + */ + private PageBook pageBook; + + /** + * The <code>Transformer</code> used to transform the value into a + * <code>Control</code>. + */ + private Transformer<?, Control> paneTransformer; + + private Label emptyLabel; + + /** + * Creates a new <code>ControlSwitcher</code>. + * + * @param switchHolder The holder of the value that will be used to retrieve + * the right <code>Control</code> when passed to the given transformer + * @param paneTransformer The <code>Transformer</code> used to transform the value into a + * <code>Control</code> + * @param pageBook The <code>Transformer</code> used to transform the value + * into a <code>Control</code> + */ + public <T> ControlSwitcher(PropertyValueModel<? extends T> switchHolder, + Transformer<T, Control> paneTransformer, + PageBook pageBook) + { + super(); + initialize(switchHolder, paneTransformer, pageBook); + } + + private void initialize(PropertyValueModel<?> switchHolder, + Transformer<?, Control> paneTransformer, + PageBook pageBook) + { + this.pageBook = pageBook; + this.paneTransformer = paneTransformer; + + this.emptyLabel = this.buildEmptyLabel(); + + switchHolder.addPropertyChangeListener( + PropertyValueModel.VALUE, + buildPropertyChangeListener() + ); + + switchPages(switchHolder.getValue()); + } + + //Build an empty label to display in the page book when the paneTransformer returns null. + //SWT.SHADOW_NONE makes the line separator not visible + //This is the best we can come up with for an empty page + private Label buildEmptyLabel() { + return new Label(this.pageBook, SWT.SEPARATOR | SWT.SHADOW_NONE | SWT.HORIZONTAL); + } + + private PropertyChangeListener buildPropertyChangeListener() { + return new SWTPropertyChangeListenerWrapper( + buildPropertyChangeListener_() + ); + } + + private PropertyChangeListener buildPropertyChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent e) { + switchPages(e.getNewValue()); + } + }; + } + + /** + * Switches the active page by transforming the given value into its + * corresponding pane. + * + * @param value The state passed to the transformer in order to retrieve the + * new pane + */ + private void switchPages(Object value) { + if (this.pageBook.isDisposed()) { + return; + } + + // Retrieve the Control for the new value + Control page = transform(value); + + if (page == null) { + //Note: We can't pass in null due to a bug in PageBook + page = this.emptyLabel; + } + this.pageBook.showPage(page); + + // Revalidate the parents in order to update the layout + SWTUtil.reflow(this.pageBook); + } + + @SuppressWarnings("unchecked") + private Control transform(Object value) { + return ((Transformer<Object, Control>) this.paneTransformer).transform(value); + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledButton.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledButton.java new file mode 100644 index 0000000000..a1d6d2979a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledButton.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: Oracle. - initial API and implementation + *******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.util; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Button; + +/** + * A default implementation of <code>LabeledControl</code> that updates a + * <code>Button</code> when required. + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public final class LabeledButton implements LabeledControl +{ + /** + * The button to be updated with a different icon and text. + */ + private final Button button; + + /** + * Creates a new <code>LabeledButton</code>. + * + * @param button The button that will have its text and icon updated when + * required + * @exception AssertionFailedException If the given <code>Button</code> is + * <code>null</code> + */ + public LabeledButton(Button button) { + super(); + + Assert.isNotNull(button, "The button cannot be null"); + this.button = button; + } + + /* + * (non-Javadoc) + */ + public void setImage(Image image) { + if (!this.button.isDisposed()) { + this.button.setImage(image); + this.button.getParent().layout(true); + } + } + + /* + * (non-Javadoc) + */ + public void setText(String text) { + if (!this.button.isDisposed()) { + this.button.setText(text); + this.button.getParent().layout(true); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledControl.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledControl.java new file mode 100644 index 0000000000..2cef0dc8b2 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledControl.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: Oracle. - initial API and implementation + *******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.util; + +import org.eclipse.swt.graphics.Image; + +/** + * This <code>LabeledControl</code> is used to encapsulate a widget and update + * its properties (icon and text). + * + * @see LabeledButton + * @see LabeledLabel + * + * @version 2.0 + * @since 2.0 + */ +public interface LabeledControl { + /** + * Passes the image so the wrapped component can receive it. + * + * @param image The new <code>Image</code> + */ + void setImage(Image image); + + /** + * Passes the text so the wrapped component can receive it. + * + * @param text The new text + */ + void setText(String text); +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledControlUpdater.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledControlUpdater.java new file mode 100644 index 0000000000..fdad6f1cff --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledControlUpdater.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.util; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.graphics.Image; + +/** + * This updater is responsible to update the <code>LabeledControl</code> when + * the text and the icon need to change. + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public final class LabeledControlUpdater { + + /** + * The wrapper around a control that has text and icon. + */ + private LabeledControl labeledControl; + + /** + * Creates a new <code>LabeledControlUpdater</code>. + * + * @param labeledControl The wrapper around the control that needs to + * have its text updated + * @param textHolder The holder this class will listen for changes + */ + public LabeledControlUpdater(LabeledControl labeledControl, + PropertyValueModel<String> textHolder) + { + this(labeledControl, textHolder, null); + } + + /** + * Creates a new <code>LabeledControlUpdater</code>. + * + * @param labeledControl The wrapper around the control that needs to + * have its image and text updated + * @param imageHolder The holder this class will listen for changes or + * <code>null</code> if the text never changes + * @param textHolder The holder this class will listen for changes or + * <code>null</code> if the image never changes + */ + public LabeledControlUpdater(LabeledControl labeledControl, + PropertyValueModel<String> textHolder, + PropertyValueModel<Image> imageHolder) + { + super(); + initialize(labeledControl, textHolder, imageHolder); + } + + private PropertyChangeListener buildIconListener() { + return new SWTPropertyChangeListenerWrapper(buildIconListener_()); + } + + private PropertyChangeListener buildIconListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent e) { + LabeledControlUpdater.this.setImage((Image) e.getNewValue()); + } + + @Override + public String toString() { + return "LabeledControlUpdater.imageListener"; + } + }; + } + + private PropertyChangeListener buildTextListener() { + return new SWTPropertyChangeListenerWrapper(buildTextListener_()); + } + + private PropertyChangeListener buildTextListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent e) { + LabeledControlUpdater.this.setText((String) e.getNewValue()); + } + + @Override + public String toString() { + return "LabeledControlUpdater.textListener"; + } + }; + } + + private void initialize(LabeledControl labeledControl, + PropertyValueModel<String> textHolder, + PropertyValueModel<Image> imageHolder) + { + Assert.isNotNull(labeledControl, "The LabeledControl cannot be null"); + + this.labeledControl = labeledControl; + + if (textHolder != null) { + textHolder.addPropertyChangeListener(PropertyValueModel.VALUE, buildTextListener()); + setText(textHolder.getValue()); + } + + if (imageHolder != null) { + imageHolder.addPropertyChangeListener(PropertyValueModel.VALUE, buildIconListener()); + setImage(imageHolder.getValue()); + } + } + + private void setImage(Image icon) { + labeledControl.setImage(icon); + } + + private void setText(String text) { + + if (text == null) { + text = ""; + } + + labeledControl.setText(text); + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledLabel.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledLabel.java new file mode 100644 index 0000000000..c74ef06559 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledLabel.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: Oracle. - initial API and implementation + *******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.util; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Label; + +/** + * A default implementation of <code>LabeledControl</code> that updates an + * <code>Label</code> when required. + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public final class LabeledLabel implements LabeledControl +{ + /** + * The label to be updated with a different icon and text. + */ + private final Label label; + + /** + * Creates a new <code>LabeledButton</code>. + * + * @param label The label that will have its text and icon updated when + * required + * @exception AssertionFailedException If the given <code>Label</code> is + * <code>null</code> + */ + public LabeledLabel(Label label) { + super(); + + Assert.isNotNull(label, "The label cannot be null"); + this.label = label; + } + + /* + * (non-Javadoc) + */ + public void setImage(Image image) { + if (!this.label.isDisposed()) { + this.label.setImage(image); + this.label.getParent().layout(true); + } + } + + /* + * (non-Javadoc) + */ + public void setText(String text) { + if (!this.label.isDisposed()) { + this.label.setText(text); + this.label.getParent().layout(true); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/PaneEnabler.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/PaneEnabler.java new file mode 100644 index 0000000000..94f2001586 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/PaneEnabler.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.util; + +import java.util.Collection; +import java.util.Iterator; +import org.eclipse.jpt.common.ui.internal.widgets.Pane; +import org.eclipse.jpt.utility.internal.CollectionTools; +import org.eclipse.jpt.utility.internal.iterators.TransformationIterator; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; + +/** + * This <code>PaneEnabler</code> keeps the "enabled" state of a collection of + * controls in synch with the provided boolean holder. + * + * @version 2.0 + * @since 2.0 + */ +public class PaneEnabler extends StateController +{ + /** + * Creates a new <code>PaneEnabler</code> with a default value of + * <code>false</code> (i.e. disabled). + * + * @param booleanHolder A value model on the underlying boolean model + * @param pane The pane whose "enabled" state is kept in sync with the + * boolean holder's value + */ + public PaneEnabler(PropertyValueModel<Boolean> booleanHolder, + Pane<?> pane) { + + this(booleanHolder, pane, false); + } + + /** + * Creates a new <code>PaneEnabler</code> with a default value of + * <code>false</code> (i.e. disabled). + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes The collection of panes whose "enabled" state is kept in sync + * with the boolean holder's value + */ + public PaneEnabler(PropertyValueModel<Boolean> booleanHolder, + Pane<?>... panes) { + + this(booleanHolder, CollectionTools.collection(panes), false); + } + + /** + * Creates a new <code>PaneEnabler</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param pane The pane whose "enabled" state is kept in sync with the + * boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + public PaneEnabler(PropertyValueModel<Boolean> booleanHolder, + Pane<?> pane, + boolean defaultValue) { + + this(booleanHolder, CollectionTools.singletonIterator(pane), false); + } + + /** + * Creates a new <code>PaneEnabler</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes The collection of panes whose "enabled" state is kept in sync + * with the boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + public PaneEnabler(PropertyValueModel<Boolean> booleanHolder, + Pane<?>[] panes, + boolean defaultValue) { + + this(booleanHolder, CollectionTools.iterator(panes), defaultValue); + } + + /** + * Creates a new <code>BaseJpaControllerEnabler</code> with a default value + * of* <code>false</code> (i.e. disabled). + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes The collection of panes whose "enabled" state is kept in sync + * with the boolean holder's value + */ + public PaneEnabler(PropertyValueModel<Boolean> booleanHolder, + Collection<? extends Pane<?>> panes) { + + this(booleanHolder, panes, false); + } + + /** + * Creates a new <code>BaseJpaControllerEnabler</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes The collection of panes whose "enabled" state is kept in sync + * with the boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + public PaneEnabler(PropertyValueModel<Boolean> booleanHolder, + Collection<? extends Pane<?>> panes, + boolean defaultValue) { + + this(booleanHolder, panes.iterator(), defaultValue); + } + + /** + * Creates a new <code>BaseJpaControllerEnabler</code> with a default value of + * <code>false</code> (i.e. disabled). + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes An iterator on the collection of panes whose "enabled" state + * is kept in sync with the boolean holder's value + */ + public PaneEnabler(PropertyValueModel<Boolean> booleanHolder, + Iterator<? extends Pane<?>> panes) { + + this(booleanHolder, panes, false); + } + + /** + * Creates a new <code>BaseJpaControllerEnabler</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes An iterator on the collection of panes whose "enabled" state + * is kept in sync with the boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + public PaneEnabler(PropertyValueModel<Boolean> booleanHolder, + Iterator<? extends Pane<?>> panes, + boolean defaultValue) { + + super(booleanHolder, wrap(panes), defaultValue); + } + + private static Collection<ControlHolder> wrap(Iterator<? extends Pane<?>> panes) { + return CollectionTools.collection(new TransformationIterator<Pane<?>, ControlHolder>(panes) { + @Override + protected ControlHolder transform(Pane<?> pane) { + return new PaneHolder(pane); + } + }); + } + + /** + * This holder holds onto an <code>Pane</code> and update its enabled + * state. + */ + private static class PaneHolder implements ControlHolder { + private final Pane<?> pane; + + PaneHolder(Pane<?> pane) { + super(); + this.pane = pane; + } + + public void updateState(boolean state) { + if (!this.pane.getControl().isDisposed()) { + this.pane.enableWidgets(state); + } + } + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/PaneVisibilityEnabler.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/PaneVisibilityEnabler.java new file mode 100644 index 0000000000..f799f12795 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/PaneVisibilityEnabler.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.util; + +import java.util.Collection; +import java.util.Iterator; +import org.eclipse.jpt.common.ui.internal.widgets.Pane; +import org.eclipse.jpt.utility.internal.CollectionTools; +import org.eclipse.jpt.utility.internal.iterators.TransformationIterator; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; + +/** + * This <code>PaneVisibilityEnabler</code> keeps the "visible" state of a + * collection of controls in synch with the provided boolean holder. + * + * @version 2.0 + * @since 2.0 + */ +public class PaneVisibilityEnabler extends StateController +{ + /** + * Creates a new <code>PaneVisibilityEnabler</code> with a default value of + * <code>false</code> (i.e. not visible). + * + * @param booleanHolder A value model on the underlying boolean model + * @param pane The pane whose "visible" state is kept in sync with the + * boolean holder's value + */ + public PaneVisibilityEnabler(PropertyValueModel<Boolean> booleanHolder, + Pane<?> pane) { + + this(booleanHolder, pane, false); + } + + /** + * Creates a new <code>PaneVisibilityEnabler</code> with a default value of + * <code>false</code> (i.e. not visible). + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes The collection of panes whose "visible" state is kept in sync + * with the boolean holder's value + */ + public PaneVisibilityEnabler(PropertyValueModel<Boolean> booleanHolder, + Pane<?>... panes) { + + this(booleanHolder, CollectionTools.collection(panes), false); + } + + /** + * Creates a new <code>PaneVisibilityEnabler</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param pane The pane whose "visible" state is kept in sync with the + * boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + public PaneVisibilityEnabler(PropertyValueModel<Boolean> booleanHolder, + Pane<?> pane, + boolean defaultValue) { + + this(booleanHolder, CollectionTools.singletonIterator(pane), false); + } + + /** + * Creates a new <code>PaneVisibilityEnabler</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes The collection of panes whose "visible" state is kept in sync + * with the boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + public PaneVisibilityEnabler(PropertyValueModel<Boolean> booleanHolder, + Pane<?>[] panes, + boolean defaultValue) { + + this(booleanHolder, CollectionTools.iterator(panes), defaultValue); + } + + /** + * Creates a new <code>PaneVisibilityEnabler</code> with a default value of + * <code>false</code> (i.e. not visible). + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes The collection of panes whose "visible" state is kept in sync + * with the boolean holder's value + */ + public PaneVisibilityEnabler(PropertyValueModel<Boolean> booleanHolder, + Collection<? extends Pane<?>> panes) { + + this(booleanHolder, panes, false); + } + + /** + * Creates a new <code>PaneVisibilityEnabler</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes The collection of panes whose "visible" state is kept in sync + * with the boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + public PaneVisibilityEnabler(PropertyValueModel<Boolean> booleanHolder, + Collection<? extends Pane<?>> panes, + boolean defaultValue) { + + this(booleanHolder, panes.iterator(), defaultValue); + } + + /** + * Creates a new <code>PaneVisibilityEnabler</code> with a default value of + * <code>false</code> (i.e. not visible). + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes An iterator on the collection of panes whose "visible" state + * is kept in sync with the boolean holder's value + */ + public PaneVisibilityEnabler(PropertyValueModel<Boolean> booleanHolder, + Iterator<? extends Pane<?>> panes) { + + this(booleanHolder, panes, false); + } + + /** + * Creates a new <code>PaneVisibilityEnabler</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param panes An iterator on the collection of panes whose "visible" state + * is kept in sync with the boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + public PaneVisibilityEnabler(PropertyValueModel<Boolean> booleanHolder, + Iterator<? extends Pane<?>> panes, + boolean defaultValue) { + + super(booleanHolder, wrap(panes), defaultValue); + } + + private static Collection<ControlHolder> wrap(Iterator<? extends Pane<?>> panes) { + return CollectionTools.collection(new TransformationIterator<Pane<?>, ControlHolder>(panes) { + @Override + protected ControlHolder transform(Pane<?> pane) { + return new PaneHolder(pane); + } + }); + } + + /** + * This holder holds onto an <code>Pane</code> and update its visible + * state. + */ + private static class PaneHolder implements ControlHolder { + private final Pane<?> pane; + + PaneHolder(Pane<?> pane) { + super(); + this.pane = pane; + } + + public void updateState(boolean state) { + this.pane.setVisible(state); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/SWTUtil.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/SWTUtil.java new file mode 100644 index 0000000000..f699b306af --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/SWTUtil.java @@ -0,0 +1,447 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.util; + +import java.util.Locale; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.AssertionFailedException; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jpt.common.ui.internal.widgets.NullPostExecution; +import org.eclipse.jpt.common.ui.internal.widgets.PostExecution; +import org.eclipse.jpt.utility.internal.ReflectionTools; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.forms.widgets.ScrolledForm; + +/** + * A suite of utility methods related to the user interface. + * + * @version 2.0 + * @since 1.0 + */ +@SuppressWarnings("nls") +public class SWTUtil { + + /** + * Causes the <code>run()</code> method of the given runnable to be invoked + * by the user-interface thread at the next reasonable opportunity. The caller + * of this method continues to run in parallel, and is not notified when the + * runnable has completed. + * + * @param runnable Code to run on the user-interface thread + * @exception org.eclipse.swt.SWTException + * <ul> + * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li> + * </ul> + * @see #syncExec + */ + public static void asyncExec(Runnable runnable) { + getStandardDisplay().asyncExec(runnable); + } + + /** + * Tweaks the given <code>Combo</code> to remove the default value when the + * widget receives the focus and to show the default when the widget loses + * the focus. + * + * @param combo The widget having a default value that is always at the + * beginning of the list + */ + public static void attachDefaultValueHandler(Combo combo) { + ComboHandler handler = new ComboHandler(); + combo.addFocusListener(handler); + combo.addModifyListener(handler); + } + + /** + * Retrieves the localized string from the given NLS class by creating the + * key. That key is the concatenation of the composite's short class name + * with the toString() of the given value separated by an underscore. + * + * @param nlsClass The NLS class used to retrieve the localized text + * @param compositeClass The class used for creating the key, its short class + * name is the beginning of the key + * @param value The value used to append its toString() to the generated key + * @return The localized text associated with the value + */ + public static String buildDisplayString(Class<?> nlsClass, + Class<?> compositeClass, + Object value) { + + StringBuilder sb = new StringBuilder(); + sb.append(compositeClass.getSimpleName()); + sb.append("_"); + sb.append(value.toString().toLowerCase(Locale.ENGLISH));//bug 234953 + //TODO in a future release we should not be converting the key using toLowerCase() + + return (String) ReflectionTools.getStaticFieldValue(nlsClass, sb.toString()); + } + + /** + * Retrieves the localized string from the given NLS class by creating the + * key. That key is the concatenation of the composite's short class name + * with the toString() of the given value separated by an underscore. + * + * @param nlsClass The NLS class used to retrieve the localized text + * @param composite The object used to retrieve the short class name that is + * the beginning of the key + * @param value The value used to append its toString() to the generated key + * @return The localized text associated with the value + */ + public static final String buildDisplayString(Class<?> nlsClass, + Object composite, + Object value) { + + return buildDisplayString(nlsClass, composite.getClass(), value); + } + + /** + * Creates the <code>Runnable</code> that will invoke the given + * <code>PostExecution</code> in order to its execution to be done in the + * UI thread. + * + * @param dialog The dialog that was just diposed + * @param postExecution The post execution once the dialog is disposed + * @return The <code>Runnable</code> that will invoke + * {@link PostExecution#execute(Dialog)} + */ + @SuppressWarnings("unchecked") + private static <D1 extends Dialog, D2 extends D1> + Runnable buildPostExecutionRunnable( + final D1 dialog, + final PostExecution<D2> postExecution) { + + return new Runnable() { + public void run() { + setUserInterfaceActive(false); + try { + postExecution.execute((D2) dialog); + } + finally { + setUserInterfaceActive(true); + } + } + }; + } + + /** + * Convenience method for getting the current shell. If the current thread is + * not the UI thread, then an invalid thread access exception will be thrown. + * + * @return The shell, never <code>null</code> + */ + public static Shell getShell() { + + // Retrieve the active shell, which can be the shell from any window + Shell shell = getStandardDisplay().getActiveShell(); + + // No shell could be found, revert back to the active workbench window + if (shell == null) { + shell = getWorkbench().getActiveWorkbenchWindow().getShell(); + } + + // Make sure it's never null + if (shell == null) { + shell = new Shell(getStandardDisplay().getActiveShell()); + } + + return shell; + } + + /** + * Returns the standard display to be used. The method first checks, if the + * thread calling this method has an associated display. If so, this display + * is returned. Otherwise the method returns the default display. + * + * @return The current display if not <code>null</code> otherwise the default + * display is returned + */ + public static Display getStandardDisplay() + { + Display display = Display.getCurrent(); + + if (display == null) { + display = Display.getDefault(); + } + + return display; + } + + public static int getTableHeightHint(Table table, int rows) { + if (table.getFont().equals(JFaceResources.getDefaultFont())) + table.setFont(JFaceResources.getDialogFont()); + int result= table.getItemHeight() * rows + table.getHeaderHeight(); + if (table.getLinesVisible()) + result+= table.getGridLineWidth() * (rows - 1); + return result; + } + + /** + * Returns the Platform UI workbench. + * + * @return The workbench for this plug-in + */ + public static IWorkbench getWorkbench() { + return PlatformUI.getWorkbench(); + } + + /** + * Relays out the parents of the <code>Control</code>. This was taken from + * the widget <code>Section</code>. + * + * @param pane The pane to revalidate as well as its parents + */ + public static void reflow(Composite pane) { + + for (Composite composite = pane; composite != null; ) { + composite.setRedraw(false); + composite = composite.getParent(); + + if (composite instanceof ScrolledForm || composite instanceof Shell) { + break; + } + } + + for (Composite composite = pane; composite != null; ) { + composite.layout(true); + composite = composite.getParent(); + + if (composite instanceof ScrolledForm) { + ((ScrolledForm) composite).reflow(true); + break; + } + } + + for (Composite composite = pane; composite != null; ) { + composite.setRedraw(true); + composite = composite.getParent(); + + if (composite instanceof ScrolledForm || composite instanceof Shell) { + break; + } + } + } + + /** + * Sets whether the entire shell and its widgets should be enabled or + * everything should be unaccessible. + * + * @param active <code>true</code> to make all the UI active otherwise + * <code>false</code> to deactivate it + */ + public static void setUserInterfaceActive(boolean active) { + Shell[] shells = getStandardDisplay().getShells(); + + for (Shell shell : shells) { + shell.setEnabled(active); + } + } + + /** + * Asynchronously launches the specified dialog in the UI thread. + * + * @param dialog The dialog to show on screen + * @param postExecution This interface let the caller to invoke a piece of + * code once the dialog is disposed + */ + public static <D1 extends Dialog, D2 extends D1> + void show(final D1 dialog, final PostExecution<D2> postExecution) { + + try { + Assert.isNotNull(dialog, "The dialog cannot be null"); + Assert.isNotNull(postExecution, "The PostExecution cannot be null"); + } + catch (AssertionFailedException e) { + // Make sure the UI is interactive + setUserInterfaceActive(true); + throw e; + } + + new Thread() { + @Override + public void run() { + asyncExec( + new Runnable() { + public void run() { + showImp(dialog, postExecution); + } + } + ); + } + }.start(); + } + + /** + * Asynchronously launches the specified dialog in the UI thread. + * + * @param dialog The dialog to show on screen + */ + public static void show(Dialog dialog) { + show(dialog, NullPostExecution.<Dialog>instance()); + } + + /** + * Asynchronously launches the specified dialog in the UI thread. + * + * @param dialog The dialog to show on screen + * @param postExecution This interface let the caller to invoke a piece of + * code once the dialog is disposed + */ + private static <D1 extends Dialog, D2 extends D1> + void showImp(D1 dialog, PostExecution<D2> postExecution) { + + setUserInterfaceActive(true); + dialog.open(); + + if (postExecution != NullPostExecution.<D2>instance()) { + asyncExec(buildPostExecutionRunnable(dialog, postExecution)); + } + } + + /** + * Causes the <code>run()</code> method of the given runnable to be invoked + * by the user-interface thread at the next reasonable opportunity. The + * thread which calls this method is suspended until the runnable completes. + * + * @param runnable code to run on the user-interface thread. + * @see #asyncExec + */ + public static void syncExec(Runnable runnable) { + getStandardDisplay().syncExec(runnable); + } + + /** + * Determines if the current thread is the UI event thread. + * + * @return <code>true</code> if it's the UI event thread, <code>false</code> + * otherwise + */ + public static boolean uiThread() { + return getStandardDisplay().getThread() == Thread.currentThread(); + } + + /** + * Determines if the current thread is the UI event thread by using the + * thread from which the given viewer's display was instantiated. + * + * @param viewer The viewer used to determine if the current thread + * is the UI event thread + * @return <code>true</code> if the current thread is the UI event thread; + * <code>false</code> otherwise + */ + public static boolean uiThread(Viewer viewer) { + return uiThread(viewer.getControl()); + } + + /** + * Determines if the current thread is the UI event thread by using the + * thread from which the given widget's display was instantiated. + * + * @param widget The widget used to determine if the current thread + * is the UI event thread + * @return <code>true</code> if the current thread is the UI event thread; + * <code>false</code> otherwise + */ + public static boolean uiThread(Widget widget) { + return widget.getDisplay().getThread() == Thread.currentThread(); + } + + + /** + * This handler is responsible for removing the default value when the combo + * has the focus or when the selected item is the default value and to select + * it when the combo loses the focus. + */ + private static class ComboHandler implements ModifyListener, + FocusListener { + + public void focusGained(FocusEvent e) { + Combo combo = (Combo) e.widget; + + if (combo.getSelectionIndex() == 0) { + // The text selection has to be changed outside of the context of this + // listener otherwise the combo won't update because it's currently + // notifying its listeners + asyncExec(new SelectText(combo)); + } + } + + public void focusLost(FocusEvent e) { + //do nothing + } + + public void modifyText(ModifyEvent e) { + + Combo combo = (Combo) e.widget; + + if (combo.isFocusControl() && + combo.getSelectionIndex() == 0) { + + // The text has to be changed outside of the context of this + // listener otherwise the combo won't update because it's currently + // notifying its listeners + asyncExec(new ModifyText(combo)); + } + } + + private class ModifyText implements Runnable { + private final Combo combo; + + public ModifyText(Combo combo) { + super(); + this.combo = combo; + } + + public void run() { + if (this.combo.isDisposed()) { + return; + } + String text = this.combo.getText(); + + if (text.length() == 0) { + text = this.combo.getItem(0); + this.combo.setText(text); + } + + this.combo.setSelection(new Point(0, text.length())); + } + } + + private class SelectText implements Runnable { + private final Combo combo; + + public SelectText(Combo combo) { + super(); + this.combo = combo; + } + + public void run() { + if (this.combo.isDisposed()) { + return; + } + String text = this.combo.getText(); + this.combo.setSelection(new Point(0, text.length())); + } + } + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/StateController.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/StateController.java new file mode 100644 index 0000000000..2d02d0b409 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/StateController.java @@ -0,0 +1,320 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.CollectionTools; +import org.eclipse.jpt.utility.internal.iterators.CloneIterator; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; + +/** + * A <code>StateController</code> keeps the state of a collection of widgets in + * synch with the provided boolean holder. + * + * @see ControlEnabler + * @see ControlVisibilityEnabler + * @see PaneEnabler + * @see PaneVisibilityEnabler + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +abstract class StateController +{ + /** + * A listener that allows us to synchronize the controlHolders with changes + * made to the underlying boolean model. + */ + private PropertyChangeListener booleanChangeListener; + + /** + * A value model on the underlying boolean model + */ + private PropertyValueModel<Boolean> booleanHolder; + + /** + * The collection of <code>ControlHolder</code>s whose state is kept in sync + * with the boolean holder's value. + */ + private Collection<ControlHolder> controlHolders; + + /** + * The default setting for the state; for when the underlying model is + * <code>null</code>. The default [default value] is <code>false<code>. + */ + private boolean defaultValue; + + /** + * Creates a new <code>StateController</code>. + */ + StateController() { + super(); + initialize(); + } + + /** + * Creates a new <code>StateController</code> with a default value of + * <code>false</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param controlHolders The collection of <code>ControlHolder</code>s whose + * state is kept in sync with the boolean holder's value + */ + StateController(PropertyValueModel<Boolean> booleanHolder, + Collection<ControlHolder> controlHolders) { + + this(booleanHolder, controlHolders, false); + } + + /** + * Creates a new <code>StateController</code> with a default value of + * <code>false</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param controlHolders The collection of <code>ControlHolder</code>s whose + * state is kept in sync with the boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + StateController(PropertyValueModel<Boolean> booleanHolder, + Collection<ControlHolder> controlHolders, + boolean defaultValue) { + + this(); + initialize(booleanHolder, controlHolders, defaultValue); + } + + /** + * Creates a new <code>StateController</code> with a default value of + * <code>false</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param controlHolder The <code>ControlHolder</code> whose state is kept + * in sync with the boolean holder's value + */ + StateController(PropertyValueModel<Boolean> booleanHolder, + ControlHolder controlHolder) { + + this(booleanHolder, controlHolder, false); + } + + /** + * Creates a new <code>StateController</code> with a default value of + * <code>false</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param controlHolders The collection of <code>ControlHolder</code>s whose + * state is kept in sync with the boolean holder's value + */ + StateController(PropertyValueModel<Boolean> booleanHolder, + ControlHolder... controlHolders) { + + this(booleanHolder, CollectionTools.collection(controlHolders), false); + } + + /** + * Creates a new <code>StateController</code> with a default value of + * <code>false</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param controlHolder The <code>ControlHolder</code> whose state is kept + * in sync with the boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + StateController(PropertyValueModel<Boolean> booleanHolder, + ControlHolder controlHolder, + boolean defaultValue) { + + this(booleanHolder, new ControlHolder[] { controlHolder }, false); + } + + /** + * Creates a new <code>StateController</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param controlHolders The collection of <code>ControlHolder</code>s whose + * state is kept in sync with the boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + StateController(PropertyValueModel<Boolean> booleanHolder, + ControlHolder[] controlHolders, + boolean defaultValue) { + + this(); + this.initialize(booleanHolder, CollectionTools.collection(controlHolders), defaultValue); + } + + /** + * Creates a new <code>StateController</code> with a default value of + * <code>false</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param controlHolders An iterator on the collection of + * <code>ControlHolder</code>s whose state is kept in sync with the boolean + * holder's value + */ + StateController(PropertyValueModel<Boolean> booleanHolder, + Iterator<ControlHolder> controlHolders) { + + this(booleanHolder, CollectionTools.collection(controlHolders), false); + } + + /** + * Creates a new <code>StateController</code>. + * + * @param booleanHolder A value model on the underlying boolean model + * @param controlHolders An iterator on the collection of + * <code>ControlHolder</code>s whose state is kept in sync with the boolean + * holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + StateController(PropertyValueModel<Boolean> booleanHolder, + Iterator<ControlHolder> controlHolders, + boolean defaultValue) { + + this(); + initialize(booleanHolder, CollectionTools.collection(controlHolders), defaultValue); + } + + /** + * Returns the boolean primitive of the given <code>Boolean</code> value but + * also checks for <code>null</code>, if that is the case, then + * {@link #defaultValue} is returned. + * + * @param value The <code>Boolean</code> value to be returned as a primitive + * @return The primitive of the given value or {@link #defaultValue}when the + * value is <code>null</code> + */ + protected boolean booleanValue(Boolean value) { + return (value == null) ? this.defaultValue : value.booleanValue(); + } + + /** + * Creates a listener for the boolean holder. + * + * @return A new <code>PropertyChangeListener</code> + */ + private PropertyChangeListener buildBooleanChangeListener() { + return new SWTPropertyChangeListenerWrapper( + buildBooleanChangeListener_() + ) + { + @Override + public String toString() { + return "StateController.SWTPropertyChangeListenerWrapper"; + } + }; + } + + /** + * Creates a listener for the boolean holder. + * + * @return A new <code>PropertyChangeListener</code> + */ + private PropertyChangeListener buildBooleanChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + updateState(); + } + + @Override + public String toString() { + return "StateController.PropertyChangeListener"; + } + }; + } + + /** + * Returns an <code>Iterator</code> over the collection of + * <code>ControlHolder</code>s. + * + * @return The iteration of <code>ControlHolder</code>s + */ + protected final Iterator<ControlHolder> controlHolders() { + return new CloneIterator<ControlHolder>(this.controlHolders); + } + + /** + * Initializes this <code>StateController</code> by building the appropriate + * listeners. + */ + protected void initialize() { + this.booleanChangeListener = this.buildBooleanChangeListener(); + } + + /** + * Initializes this <code>StateController</code> with the given state. + * + * @param booleanHolder A value model on the underlying boolean model + * @param controlHolders A <code>ControlHolder</code>s whose enablement state + * is kept in sync with the boolean holder's value + * @param defaultValue The value to use when the underlying model is + * <code>null</code> + */ + protected void initialize(PropertyValueModel<Boolean> booleanHolder, + Collection<ControlHolder> controlHolders, + boolean defaultValue) { + + Assert.isNotNull(booleanHolder, "The holder of the boolean value cannot be null"); + Assert.isNotNull(controlHolders, "The collection of ControlHolders cannot be null"); + + this.controlHolders = new ArrayList<ControlHolder>(controlHolders); + this.defaultValue = defaultValue; + this.booleanHolder = booleanHolder; + + this.booleanHolder.addPropertyChangeListener( + PropertyValueModel.VALUE, + this.booleanChangeListener + ); + + this.updateState(); + } + + /** + * Updates the state of the control holders. + */ + protected void updateState() { + this.updateState(booleanValue(this.booleanHolder.getValue())); + } + + /** + * Updates the state of the <code>Control</code>s. + * + * @param state The new state the widgets need to have + */ + protected void updateState(boolean state) { + for (ControlHolder controlHolder : this.controlHolders) { + controlHolder.updateState(state); + } + } + + /** + * The holder of the actual widget. + */ + static interface ControlHolder { + + /** + * Updates the state of the wrapped control. + * + * @param state The new state the control should have + */ + void updateState(boolean state); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/TableLayoutComposite.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/TableLayoutComposite.java new file mode 100644 index 0000000000..9a8790c941 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/TableLayoutComposite.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 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 + *******************************************************************************/ +// copied from org.eclipse.jdt.internal.ui.util.TableLayoutComposite +package org.eclipse.jpt.common.ui.internal.util; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; + +import org.eclipse.core.runtime.Assert; + +import org.eclipse.jface.viewers.ColumnLayoutData; +import org.eclipse.jface.viewers.ColumnPixelData; +import org.eclipse.jface.viewers.ColumnWeightData; + +/** + * A special composite to layout columns inside a table. The composite is needed since we have + * to layout the columns "before" the actual table gets layouted. Hence we can't use a normal + * layout manager. + * <p> + * XXX: Should switch to use {@link org.eclipse.jface.layout.TableColumnLayout}. + * </p> + */ +public class TableLayoutComposite extends Composite { + + /** + * The number of extra pixels taken as horizontal trim by the table column. + * To ensure there are N pixels available for the content of the column, + * assign N+COLUMN_TRIM for the column width. + * <p> + * XXX: Should either switch to use {@link org.eclipse.jface.layout.TableColumnLayout} or get API from JFace or SWT, see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=218483 + * </p> + * + * @since 3.1 + */ + private static int COLUMN_TRIM; + static { + String platform= SWT.getPlatform(); + if ("win32".equals(platform)) //$NON-NLS-1$ + COLUMN_TRIM= 4; + else if ("carbon".equals(platform)) //$NON-NLS-1$ + COLUMN_TRIM= 24; + else + COLUMN_TRIM= 3; + } + + private List columns= new ArrayList(); + + /** + * Creates a new <code>TableLayoutComposite</code>. + * + * @param parent the parent composite + * @param style the SWT style + */ + public TableLayoutComposite(Composite parent, int style) { + super(parent, style); + addControlListener(new ControlAdapter() { + public void controlResized(ControlEvent e) { + Rectangle area= getClientArea(); + Table table= (Table)getChildren()[0]; + Point preferredSize= computeTableSize(table); + int width= area.width - 2 * table.getBorderWidth(); + if (preferredSize.y > area.height) { + // Subtract the scrollbar width from the total column width + // if a vertical scrollbar will be required + Point vBarSize = table.getVerticalBar().getSize(); + width -= vBarSize.x; + } + layoutTable(table, width, area, table.getSize().x < area.width); + } + }); + } + + /** + * Adds a new column of data to this table layout. + * + * @param data the column layout data + */ + public void addColumnData(ColumnLayoutData data) { + columns.add(data); + } + + //---- Helpers ------------------------------------------------------------------------------------- + + private Point computeTableSize(Table table) { + Point result= table.computeSize(SWT.DEFAULT, SWT.DEFAULT); + + int width= 0; + int size= columns.size(); + for (int i= 0; i < size; ++i) { + ColumnLayoutData layoutData= (ColumnLayoutData) columns.get(i); + if (layoutData instanceof ColumnPixelData) { + ColumnPixelData col= (ColumnPixelData) layoutData; + width += col.width; + if (col.addTrim) { + width += COLUMN_TRIM; + } + } else if (layoutData instanceof ColumnWeightData) { + ColumnWeightData col= (ColumnWeightData) layoutData; + width += col.minimumWidth; + } else { + Assert.isTrue(false, "Unknown column layout data"); //$NON-NLS-1$ + } + } + if (width > result.x) + result.x= width; + return result; + } + + private void layoutTable(Table table, int width, Rectangle area, boolean increase) { + // XXX: Layout is being called with an invalid value the first time + // it is being called on Linux. This method resets the + // Layout to null so we make sure we run it only when + // the value is OK. + if (width <= 1) + return; + + TableColumn[] tableColumns= table.getColumns(); + int size= Math.min(columns.size(), tableColumns.length); + int[] widths= new int[size]; + int fixedWidth= 0; + int numberOfWeightColumns= 0; + int totalWeight= 0; + + // First calc space occupied by fixed columns + for (int i= 0; i < size; i++) { + ColumnLayoutData col= (ColumnLayoutData) columns.get(i); + if (col instanceof ColumnPixelData) { + ColumnPixelData cpd= (ColumnPixelData) col; + int pixels= cpd.width; + if (cpd.addTrim) { + pixels += COLUMN_TRIM; + } + widths[i]= pixels; + fixedWidth += pixels; + } else if (col instanceof ColumnWeightData) { + ColumnWeightData cw= (ColumnWeightData) col; + numberOfWeightColumns++; + // first time, use the weight specified by the column data, otherwise use the actual width as the weight + // int weight = firstTime ? cw.weight : tableColumns[i].getWidth(); + int weight= cw.weight; + totalWeight += weight; + } else { + Assert.isTrue(false, "Unknown column layout data"); //$NON-NLS-1$ + } + } + + // Do we have columns that have a weight + if (numberOfWeightColumns > 0) { + // Now distribute the rest to the columns with weight. + int rest= width - fixedWidth; + int totalDistributed= 0; + for (int i= 0; i < size; ++i) { + ColumnLayoutData col= (ColumnLayoutData) columns.get(i); + if (col instanceof ColumnWeightData) { + ColumnWeightData cw= (ColumnWeightData) col; + // calculate weight as above + // int weight = firstTime ? cw.weight : tableColumns[i].getWidth(); + int weight= cw.weight; + int pixels= totalWeight == 0 ? 0 : weight * rest / totalWeight; + if (pixels < cw.minimumWidth) + pixels= cw.minimumWidth; + totalDistributed += pixels; + widths[i]= pixels; + } + } + + // Distribute any remaining pixels to columns with weight. + int diff= rest - totalDistributed; + for (int i= 0; diff > 0; ++i) { + if (i == size) + i= 0; + ColumnLayoutData col= (ColumnLayoutData) columns.get(i); + if (col instanceof ColumnWeightData) { + ++widths[i]; + --diff; + } + } + } + + if (increase) { + table.setSize(area.width, area.height); + } + for (int i= 0; i < size; i++) { + tableColumns[i].setWidth(widths[i]); + } + if (!increase) { + table.setSize(area.width, area.height); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/AsynchronousUiCommandExecutor.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/AsynchronousUiCommandExecutor.java new file mode 100644 index 0000000000..cc6e741f5b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/AsynchronousUiCommandExecutor.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility; + +import org.eclipse.jpt.utility.Command; +import org.eclipse.jpt.utility.CommandExecutor; +import org.eclipse.jpt.utility.internal.CommandRunnable; +import org.eclipse.swt.widgets.Display; + +/** + * This implementation of CommandExecutor can be used by a non-UI + * thread to asynchronously modify a JPA project with any objects associated + * with documents that are currently displayed in the UI. + */ +public final class AsynchronousUiCommandExecutor + implements CommandExecutor +{ + public static final CommandExecutor INSTANCE = new AsynchronousUiCommandExecutor(); + + public static CommandExecutor instance() { + return INSTANCE; + } + + // ensure single instance + private AsynchronousUiCommandExecutor() { + super(); + } + + public void execute(Command command) { + this.getDisplay().asyncExec(this.buildRunnable(command)); + } + + private Runnable buildRunnable(Command command) { + return new CommandRunnable(command); + } + + private Display getDisplay() { + Display display = Display.getCurrent(); + return (display != null) ? display : Display.getDefault(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/SynchronousUiCommandExecutor.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/SynchronousUiCommandExecutor.java new file mode 100644 index 0000000000..2ad237923a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/SynchronousUiCommandExecutor.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2007, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility; + +import org.eclipse.jpt.utility.Command; +import org.eclipse.jpt.utility.CommandExecutor; +import org.eclipse.jpt.utility.internal.CommandRunnable; +import org.eclipse.swt.widgets.Display; + +/** + * This implementation of CommandExecutor can be used by a non-UI + * thread to synchronously modify a JPA project with any objects associated + * with documents that are currently displayed in the UI. + */ +public final class SynchronousUiCommandExecutor + implements CommandExecutor +{ + public static final CommandExecutor INSTANCE = new SynchronousUiCommandExecutor(); + + public static CommandExecutor instance() { + return INSTANCE; + } + + // ensure single instance + private SynchronousUiCommandExecutor() { + super(); + } + + public void execute(Command command) { + this.getDisplay().syncExec(this.buildRunnable(command)); + } + + private Runnable buildRunnable(Command command) { + return new CommandRunnable(command); + } + + private Display getDisplay() { + Display display = Display.getCurrent(); + return (display != null) ? display : Display.getDefault(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/AbstractListWidgetAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/AbstractListWidgetAdapter.java new file mode 100644 index 0000000000..50df45253c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/AbstractListWidgetAdapter.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.widgets.Widget; + +/** + * All the "list widgets" are subclasses of {@link Widget}; so we can provide + * a smidgen of common behavior here. + */ +abstract class AbstractListWidgetAdapter<W extends Widget> + implements ListWidgetModelBinding.ListWidget +{ + final W widget; + + AbstractListWidgetAdapter(W widget) { + super(); + this.widget = widget; + } + + public boolean isDisposed() { + return this.widget.isDisposed(); + } + + public void addDisposeListener(DisposeListener listener) { + this.widget.addDisposeListener(listener); + } + + public void removeDisposeListener(DisposeListener listener) { + this.widget.removeDisposeListener(listener); + } + +} + diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/BooleanButtonModelBinding.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/BooleanButtonModelBinding.java new file mode 100644 index 0000000000..6fe48d12f7 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/BooleanButtonModelBinding.java @@ -0,0 +1,190 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Button; + +/** + * This binding can be used to keep a check-box, toggle button, or radio button + * "selection" synchronized with a model boolean. + * + * @see WritablePropertyValueModel + * @see Button + */ +@SuppressWarnings("nls") +final class BooleanButtonModelBinding { + + // ***** model + /** A value model on the underlying model boolean. */ + private final WritablePropertyValueModel<Boolean> booleanModel; + + /** + * A listener that allows us to synchronize the button's selection state with + * the model boolean. + */ + private final PropertyChangeListener booleanChangeListener; + + /** + * The default setting for the check-box/toggle button/radio button; + * for when the underlying model is <code>null</code>. + * The default [default value] is <code>false</code> (i.e. the check-box + * is unchecked/toggle button popped out/radio button unchecked). + */ + private final boolean defaultValue; + + // ***** UI + /** The check-box/toggle button/radio button we synchronize with the model boolean. */ + private final Button button; + + /** + * A listener that allows us to synchronize the model boolean with + * the button's selection state. + */ + private final SelectionListener buttonSelectionListener; + + /** + * A listener that allows us to stop listening to stuff when the button + * is disposed. (Critical for preventing memory leaks.) + */ + private final DisposeListener buttonDisposeListener; + + + // ********** constructor ********** + + /** + * Constructor - the boolean model and button are required. + */ + BooleanButtonModelBinding(WritablePropertyValueModel<Boolean> booleanModel, Button button, boolean defaultValue) { + super(); + if ((booleanModel == null) || (button == null)) { + throw new NullPointerException(); + } + this.booleanModel = booleanModel; + this.button = button; + this.defaultValue = defaultValue; + + this.booleanChangeListener = this.buildBooleanChangeListener(); + this.booleanModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.booleanChangeListener); + + this.buttonSelectionListener = this.buildButtonSelectionListener(); + this.button.addSelectionListener(this.buttonSelectionListener); + + this.buttonDisposeListener = this.buildButtonDisposeListener(); + this.button.addDisposeListener(this.buttonDisposeListener); + + this.setButtonSelection(this.booleanModel.getValue()); + } + + + // ********** initialization ********** + + private PropertyChangeListener buildBooleanChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildBooleanChangeListener_()); + } + + private PropertyChangeListener buildBooleanChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + BooleanButtonModelBinding.this.booleanChanged(event); + } + @Override + public String toString() { + return "boolean listener"; + } + }; + } + + private SelectionListener buildButtonSelectionListener() { + return new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + BooleanButtonModelBinding.this.buttonSelected(); + } + @Override + public String toString() { + return "button selection listener"; + } + }; + } + + private DisposeListener buildButtonDisposeListener() { + return new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + BooleanButtonModelBinding.this.buttonDisposed(); + } + @Override + public String toString() { + return "button dispose listener"; + } + }; + } + + + // ********** boolean model events ********** + + /** + * The model has changed - synchronize the button. + * If the new model value is null, use the binding's default value + * (which is typically false). + */ + /* CU private */ void booleanChanged(PropertyChangeEvent event) { + this.setButtonSelection((Boolean) event.getNewValue()); + } + + private void setButtonSelection(Boolean b) { + if ( ! this.button.isDisposed()) { + this.button.setSelection(this.booleanValue(b)); + } + } + + private boolean booleanValue(Boolean b) { + return (b != null) ? b.booleanValue() : this.defaultValue; + } + + + // ********** button events ********** + + /** + * The button has been "selected" - synchronize the model. + */ + /* CU private */ void buttonSelected() { + if ( ! this.button.isDisposed()) { + this.booleanModel.setValue(Boolean.valueOf(this.button.getSelection())); + } + } + + /* CU private */ void buttonDisposed() { + // the button is not yet "disposed" when we receive this event + // so we can still remove our listeners + this.button.removeSelectionListener(this.buttonSelectionListener); + this.button.removeDisposeListener(this.buttonDisposeListener); + this.booleanModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.booleanChangeListener); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.booleanModel); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/BooleanStateController.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/BooleanStateController.java new file mode 100644 index 0000000000..407cdc18f4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/BooleanStateController.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.widgets.Control; + +/** + * This controller enables a boolean model to control either the + * <em>enabled</em> or <em>visible</em> properties of SWT controls; i.e. the + * controls' properties are kept in synch with the boolean model, + * but <em>not</em> vice-versa. + * <p> + * Subclasses must manage the listeners; i.e. the engaging and disengaging of + * the boolean model and the control(s). + * + * @see PropertyValueModel + * @see Control#setEnabled(boolean) + * @see Control#setVisible(boolean) + */ +abstract class BooleanStateController { + + /** + * The controlling boolean model. + */ + private final PropertyValueModel<Boolean> booleanModel; + + /** + * A listener that allows us to synchronize the control states with + * changes in the value of the boolean model. + */ + private final PropertyChangeListener booleanChangeListener; + + /** + * A listener that allows us to stop listening to stuff when all the + * controls are disposed. (Critical for preventing memory leaks.) + */ + private final DisposeListener controlDisposeListener; + + /** + * The default setting for the state; for when the underlying boolean model is + * <code>null</code>. The default [default value] is <code>false<code>. + */ + private final boolean defaultValue; + + /** + * The adapter determines whether the 'enabled' or 'visible' property is + * controlled. + */ + private final Adapter adapter; + + + // ********** constructor ********** + + /** + * Constructor - the boolean model and the adapter are required. + */ + BooleanStateController(PropertyValueModel<Boolean> booleanModel, boolean defaultValue, Adapter adapter) { + super(); + if ((booleanModel == null) || (adapter == null)) { + throw new NullPointerException(); + } + this.booleanModel = booleanModel; + this.defaultValue = defaultValue; + this.adapter = adapter; + + this.booleanChangeListener = this.buildBooleanChangeListener(); + this.controlDisposeListener = this.buildControlDisposeListener(); + } + + + // ********** initialization ********** + + private PropertyChangeListener buildBooleanChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildBooleanChangeListener_()); + } + + private PropertyChangeListener buildBooleanChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + BooleanStateController.this.booleanChanged(event); + } + @Override + public String toString() { + return "boolean listener"; //$NON-NLS-1$ + } + }; + } + + private DisposeListener buildControlDisposeListener() { + return new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + // the control is not yet "disposed" when we receive this event + // so we can still remove our listener + BooleanStateController.this.controlDisposed((Control) event.widget); + } + @Override + public String toString() { + return "control dispose listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** boolean model ********** + + void engageBooleanModel() { + this.booleanModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.booleanChangeListener); + } + + void disengageBooleanModel() { + this.booleanModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.booleanChangeListener); + } + + /** + * The boolean model has changed - synchronize the controls. + * If the new boolean model value is <code>null</code>, use the controller's + * default value (which is typically false). + */ + /* CU private */ void booleanChanged(PropertyChangeEvent event) { + this.setControlState((Boolean) event.getNewValue()); + } + + + boolean getBooleanValue() { + return this.booleanValue(this.booleanModel.getValue()); + } + + private boolean booleanValue(Boolean b) { + return (b != null) ? b.booleanValue() : this.defaultValue; + } + + + // ********** control ********** + + void engageControl(Control control) { + control.addDisposeListener(this.controlDisposeListener); + } + + void disengageControl(Control control) { + control.removeDisposeListener(this.controlDisposeListener); + } + + private void setControlState(Boolean b) { + this.setControlState(this.booleanValue(b)); + } + + abstract void setControlState(boolean b); + + void setControlState(Control control, boolean b) { + if ( ! control.isDisposed()) { + this.adapter.setState(control, b); + } + } + + void controlDisposed(Control control) { + this.disengageControl(control); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.booleanModel); + } + + + // ********** adapter interface ********** + + interface Adapter { + void setState(Control control, boolean b); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/DropDownListBoxSelectionBinding.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/DropDownListBoxSelectionBinding.java new file mode 100644 index 0000000000..ff434f6377 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/DropDownListBoxSelectionBinding.java @@ -0,0 +1,283 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.internal.Tools; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; + +/** + * This binding can be used to keep a drop-down list box's selection + * synchronized with a model. The selection can be modified by either the + * drop-down list box or the model, so changes must be coordinated. + * <p> + * <strong>NB:</strong> A selected item value of <code>null</code> can be used + * to clear the drop-down list box's selection. If <code>null</code> is a + * valid item in the model list, an invalid selected item can be used to clear + * the selection. + * + * @see ListValueModel + * @see WritablePropertyValueModel + * @see DropDownListBox + * @see SWTTools + */ +@SuppressWarnings("nls") +final class DropDownListBoxSelectionBinding<E> + implements ListWidgetModelBinding.SelectionBinding +{ + // ***** model + /** + * The underlying list model. + */ + private final ListValueModel<E> listModel; + + /** + * A writable value model on the underlying model selection. + */ + private final WritablePropertyValueModel<E> selectedItemModel; + + /** + * A listener that allows us to synchronize the drop-down list box's + * selection with the model selection. + */ + private final PropertyChangeListener selectedItemChangeListener; + + // ***** UI + /** + * The drop-down list box whose selection we keep synchronized + * with the model selection. + */ + private final DropDownListBox dropdownListBox; + + /** + * A listener that allows us to synchronize our selected item holder + * with the drop-down list box's selection. + */ + private final SelectionListener dropdownListBoxSelectionListener; + + + // ********** constructor ********** + + /** + * Constructor - all parameters are required. + */ + DropDownListBoxSelectionBinding( + ListValueModel<E> listModel, + WritablePropertyValueModel<E> selectedItemModel, + DropDownListBox dropdownListBox + ) { + super(); + if ((listModel == null) || (selectedItemModel == null) || (dropdownListBox == null)) { + throw new NullPointerException(); + } + this.listModel = listModel; + this.selectedItemModel = selectedItemModel; + this.dropdownListBox = dropdownListBox; + + this.selectedItemChangeListener = this.buildSelectedItemChangeListener(); + this.selectedItemModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.selectedItemChangeListener); + + this.dropdownListBoxSelectionListener = this.buildDropDownListBoxSelectionListener(); + this.dropdownListBox.addSelectionListener(this.dropdownListBoxSelectionListener); + } + + + // ********** initialization ********** + + private PropertyChangeListener buildSelectedItemChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildSelectedItemChangeListener_()); + } + + private PropertyChangeListener buildSelectedItemChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + DropDownListBoxSelectionBinding.this.selectedItemChanged(event); + } + @Override + public String toString() { + return "selected item listener"; + } + }; + } + + private SelectionListener buildDropDownListBoxSelectionListener() { + return new SelectionListener() { + public void widgetSelected(SelectionEvent event) { + DropDownListBoxSelectionBinding.this.dropDownListBoxSelectionChanged(event); + } + public void widgetDefaultSelected(SelectionEvent event) { + DropDownListBoxSelectionBinding.this.dropDownListBoxDoubleClicked(event); + } + @Override + public String toString() { + return "drop-down list box selection listener"; + } + }; + } + + + // ********** ListWidgetModelBinding.SelectionBinding implementation ********** + + /** + * Modifying the drop-down lisb box's selected item programmatically does + * not trigger a SelectionEvent. + * <p> + * Pre-condition: The drop-down list box is not disposed. + */ + public void synchronizeListWidgetSelection() { + int oldIndex = this.dropdownListBox.getSelectionIndex(); + E value = this.selectedItemModel.getValue(); + int newIndex = this.indexOf(value); + if ((oldIndex != -1) && (newIndex != -1) && (newIndex != oldIndex)) { + this.dropdownListBox.deselect(oldIndex); + } + if (newIndex == -1) { + this.dropdownListBox.deselectAll(); + } else { + if (newIndex != oldIndex) { + this.dropdownListBox.select(newIndex); + } + } + } + + public void dispose() { + this.dropdownListBox.removeSelectionListener(this.dropdownListBoxSelectionListener); + this.selectedItemModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.selectedItemChangeListener); + } + + + // ********** selected item ********** + + void selectedItemChanged(PropertyChangeEvent event) { + if ( ! this.dropdownListBox.isDisposed()) { + this.selectedItemChanged_(event); + } + } + + /** + * Modifying the drop-down list box's selected item programmatically does + * not trigger a SelectionEvent. + */ + private void selectedItemChanged_(@SuppressWarnings("unused") PropertyChangeEvent event) { + this.synchronizeListWidgetSelection(); + } + + private int indexOf(E item) { + int len = this.listModel.size(); + for (int i = 0; i < len; i++) { + if (Tools.valuesAreEqual(this.listModel.get(i), item)) { + return i; + } + } + // if 'null' is not in the list, use it to clear the selection + if (item == null) { + return -1; + } + // We can get here via one of the following: + // 1. The selected item model is invalid and not in sync with the list + // model. This is not good and we don't make this (programming + // error) obvious (e.g. via an exception). :-( + // 2. If both the selected item model and the list model are dependent + // on the same underlying model, the selected item model may receive + // its event first, resulting in a missing item. This will resolve + // itself once the list model receives its event and synchronizes + // with the same underlying model. This situation is acceptable. + return -1; + +// This is what we used to do: +// throw new IllegalStateException("selected item not found: " + item); + } + + + // ********** combo-box events ********** + + void dropDownListBoxSelectionChanged(SelectionEvent event) { + if ( ! this.dropdownListBox.isDisposed()) { + this.dropDownListBoxSelectionChanged_(event); + } + } + + void dropDownListBoxDoubleClicked(SelectionEvent event) { + if ( ! this.dropdownListBox.isDisposed()) { + this.dropDownListBoxSelectionChanged_(event); + } + } + + private void dropDownListBoxSelectionChanged_(@SuppressWarnings("unused") SelectionEvent event) { + this.selectedItemModel.setValue(this.getDropDownListBoxSelectedItem()); + } + + private E getDropDownListBoxSelectedItem() { + int selectionIndex = this.dropdownListBox.getSelectionIndex(); + return (selectionIndex == -1) ? null : this.listModel.get(selectionIndex); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.selectedItemModel); + } + + + // ********** adapter interface ********** + + /** + * Adapter used by the drop-down list box selection binding to query and manipulate + * the drop-down list box. + */ + interface DropDownListBox { + + /** + * Return whether the combo-box is "disposed". + */ + boolean isDisposed(); + + /** + * Add the specified selection listener to the combo-box. + */ + void addSelectionListener(SelectionListener listener); + + /** + * Remove the specified selection listener from the combo-box. + */ + void removeSelectionListener(SelectionListener listener); + + /** + * Return the index of the combo-box's selection. + */ + int getSelectionIndex(); + + /** + * Select the item at the specified index in the combo-box. + */ + void select(int index); + + /** + * Deselect the item at the specified index in the combo-box. + */ + void deselect(int index); + + /** + * Clear the combo-box's selection. + */ + void deselectAll(); + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/ListBoxSelectionBinding.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/ListBoxSelectionBinding.java new file mode 100644 index 0000000000..d8b8c57610 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/ListBoxSelectionBinding.java @@ -0,0 +1,305 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.eclipse.jpt.common.ui.internal.listeners.SWTCollectionChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.ArrayTools; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.internal.Tools; +import org.eclipse.jpt.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.WritableCollectionValueModel; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.List; + +/** + * This binding can be used to keep a list box's selection + * synchronized with a model. The selection can be modified by either the list + * box or the model, so changes must be coordinated. + * + * @see ListValueModel + * @see WritableCollectionValueModel + * @see List + * @see SWTTools + */ +@SuppressWarnings("nls") +final class ListBoxSelectionBinding<E> + implements ListWidgetModelBinding.SelectionBinding +{ + // ***** model + /** + * The underlying list model. + */ + private final ListValueModel<E> listModel; + + /** + * A writable value model on the underlying model selections. + */ + private final WritableCollectionValueModel<E> selectedItemsModel; + + /** + * A listener that allows us to synchronize the list box's selection with + * the model selections. + */ + private final CollectionChangeListener selectedItemsChangeListener; + + // ***** UI + /** + * The list box whose selection we keep synchronized with the model selections. + */ + private final List listBox; + + /** + * A listener that allows us to synchronize our selected items holder + * with the list box's selection. + */ + private final SelectionListener listBoxSelectionListener; + + + // ********** constructor ********** + + /** + * Constructor - all parameters are required. + */ + ListBoxSelectionBinding( + ListValueModel<E> listModel, + WritableCollectionValueModel<E> selectedItemsModel, + List listBox + ) { + super(); + if ((listModel == null) || (selectedItemsModel == null) || (listBox == null)) { + throw new NullPointerException(); + } + this.listModel = listModel; + this.selectedItemsModel = selectedItemsModel; + this.listBox = listBox; + + this.selectedItemsChangeListener = this.buildSelectedItemsChangeListener(); + this.selectedItemsModel.addCollectionChangeListener(CollectionValueModel.VALUES, this.selectedItemsChangeListener); + + this.listBoxSelectionListener = this.buildListBoxSelectionListener(); + this.listBox.addSelectionListener(this.listBoxSelectionListener); + } + + + // ********** initialization ********** + + private CollectionChangeListener buildSelectedItemsChangeListener() { + return new SWTCollectionChangeListenerWrapper(this.buildSelectedItemsChangeListener_()); + } + + private CollectionChangeListener buildSelectedItemsChangeListener_() { + return new CollectionChangeListener() { + public void itemsAdded(CollectionAddEvent event) { + ListBoxSelectionBinding.this.selectedItemsAdded(event); + } + public void itemsRemoved(CollectionRemoveEvent event) { + ListBoxSelectionBinding.this.selectedItemsRemoved(event); + } + public void collectionCleared(CollectionClearEvent event) { + ListBoxSelectionBinding.this.selectedItemsCleared(event); + } + public void collectionChanged(CollectionChangeEvent event) { + ListBoxSelectionBinding.this.selectedItemsChanged(event); + } + @Override + public String toString() { + return "selected items listener"; + } + }; + } + + private SelectionListener buildListBoxSelectionListener() { + return new SelectionListener() { + public void widgetSelected(SelectionEvent event) { + ListBoxSelectionBinding.this.listBoxSelectionChanged(event); + } + public void widgetDefaultSelected(SelectionEvent event) { + ListBoxSelectionBinding.this.listBoxDoubleClicked(event); + } + @Override + public String toString() { + return "list box selection listener"; + } + }; + } + + + // ********** ListWidgetModelBinding.SelectionBinding implementation ********** + + /** + * Modifying the list box's selected items programmatically does not + * trigger a SelectionEvent. + * + * Pre-condition: The list-box is not disposed. + */ + public void synchronizeListWidgetSelection() { + int selectedItemsSize = this.selectedItemsModel.size(); + int[] select = new int[selectedItemsSize]; + int i = 0; + for (E item : this.selectedItemsModel) { + select[i++] = this.indexOf(item); + } + + int listSize = this.listModel.size(); + int[] deselect = new int[listSize - selectedItemsSize]; + i = 0; + for (int j = 0; j < listSize; j++) { + if ( ! ArrayTools.contains(select, j)) { + deselect[i++] = j; + } + } + + int[] old = ArrayTools.sort(this.listBox.getSelectionIndices()); + select = ArrayTools.sort(select); + if ( ! Arrays.equals(select, old)) { + this.listBox.deselect(deselect); + this.listBox.select(select); + } + } + + public void dispose() { + this.listBox.removeSelectionListener(this.listBoxSelectionListener); + this.selectedItemsModel.removeCollectionChangeListener(CollectionValueModel.VALUES, this.selectedItemsChangeListener); + } + + + // ********** selected items ********** + + void selectedItemsAdded(CollectionAddEvent event) { + if ( ! this.listBox.isDisposed()) { + this.selectedItemsAdded_(event); + } + } + + /** + * Modifying the list box's selected items programmatically does not + * trigger a SelectionEvent. + */ + private void selectedItemsAdded_(CollectionAddEvent event) { + int[] indices = new int[event.getItemsSize()]; + int i = 0; + for (E item : this.getItems(event)) { + indices[i++] = this.indexOf(item); + } + this.listBox.select(indices); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + private Iterable<E> getItems(CollectionAddEvent event) { + return (Iterable<E>) event.getItems(); + } + + void selectedItemsRemoved(CollectionRemoveEvent event) { + if ( ! this.listBox.isDisposed()) { + this.selectedItemsRemoved_(event); + } + } + + /** + * Modifying the list box's selected items programmatically does not + * trigger a SelectionEvent. + */ + private void selectedItemsRemoved_(CollectionRemoveEvent event) { + int[] indices = new int[event.getItemsSize()]; + int i = 0; + for (E item : this.getItems(event)) { + indices[i++] = this.indexOf(item); + } + this.listBox.deselect(indices); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + private Iterable<E> getItems(CollectionRemoveEvent event) { + return (Iterable<E>) event.getItems(); + } + + void selectedItemsCleared(CollectionClearEvent event) { + if ( ! this.listBox.isDisposed()) { + this.selectedItemsCleared_(event); + } + } + + /** + * Modifying the list box's selected items programmatically does not + * trigger a SelectionEvent. + */ + private void selectedItemsCleared_(@SuppressWarnings("unused") CollectionClearEvent event) { + this.listBox.deselectAll(); + } + + void selectedItemsChanged(CollectionChangeEvent event) { + if ( ! this.listBox.isDisposed()) { + this.selectedItemsChanged_(event); + } + } + + private void selectedItemsChanged_(@SuppressWarnings("unused") CollectionChangeEvent event) { + this.synchronizeListWidgetSelection(); + } + + private int indexOf(E item) { + int len = this.listModel.size(); + for (int i = 0; i < len; i++) { + if (Tools.valuesAreEqual(this.listModel.get(i), item)) { + return i; + } + } + // see comment in DropDownListBoxSelectionBinding.indexOf(E) + return -1; + } + + + // ********** list box events ********** + + void listBoxSelectionChanged(SelectionEvent event) { + if ( ! this.listBox.isDisposed()) { + this.listBoxSelectionChanged_(event); + } + } + + void listBoxDoubleClicked(SelectionEvent event) { + if ( ! this.listBox.isDisposed()) { + this.listBoxSelectionChanged_(event); + } + } + + private void listBoxSelectionChanged_(@SuppressWarnings("unused") SelectionEvent event) { + this.selectedItemsModel.setValues(this.getListBoxSelectedItems()); + } + + private Iterable<E> getListBoxSelectedItems() { + ArrayList<E> selectedItems = new ArrayList<E>(this.listBox.getSelectionCount()); + for (int selectionIndex : this.listBox.getSelectionIndices()) { + selectedItems.add(this.listModel.get(selectionIndex)); + } + return selectedItems; + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.selectedItemsModel); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/ListWidgetModelBinding.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/ListWidgetModelBinding.java new file mode 100644 index 0000000000..e402f840a3 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/ListWidgetModelBinding.java @@ -0,0 +1,428 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import java.util.ArrayList; + +import org.eclipse.jpt.common.ui.internal.listeners.SWTListChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.ArrayTools; +import org.eclipse.jpt.utility.internal.StringConverter; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.model.event.ListAddEvent; +import org.eclipse.jpt.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.utility.model.event.ListClearEvent; +import org.eclipse.jpt.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; + +/** + * This binding can be used to keep a list widget's contents + * synchronized with a model. The list widget never alters + * its contents directly; all changes are driven by the model. + * + * @see ListValueModel + * @see StringConverter + * @see ListWidget + * @see SelectionBinding + * @see SWTTools + */ +@SuppressWarnings("nls") +final class ListWidgetModelBinding<E> { + + // ***** model + /** + * The underlying list model. + */ + private final ListValueModel<E> listModel; + + /** + * A listener that allows us to synchronize the list widget's contents with + * the model list. + */ + private final ListChangeListener listChangeListener; + + /** + * A converter that converts items in the model list + * to strings that can be put in the list widget. + */ + private final StringConverter<E> stringConverter; + + // ***** UI + /** + * An adapter on the list widget we keep synchronized with the model list. + */ + private final ListWidget listWidget; + + /** + * A listener that allows us to stop listening to stuff when the list widget + * is disposed. (Critical for preventing memory leaks.) + */ + private final DisposeListener listWidgetDisposeListener; + + // ***** selection + /** + * Widget-specific selection binding. + */ + private final SelectionBinding selectionBinding; + + + // ********** constructor ********** + + /** + * Constructor - all parameters are required. + */ + ListWidgetModelBinding( + ListValueModel<E> listModel, + ListWidget listWidget, + StringConverter<E> stringConverter, + SelectionBinding selectionBinding + ) { + super(); + if ((listModel == null) || (listWidget == null) || (stringConverter == null) || (selectionBinding == null)) { + throw new NullPointerException(); + } + this.listModel = listModel; + this.listWidget = listWidget; + this.stringConverter = stringConverter; + this.selectionBinding = selectionBinding; + + this.listChangeListener = this.buildListChangeListener(); + this.listModel.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + + this.listWidgetDisposeListener = this.buildListWidgetDisposeListener(); + this.listWidget.addDisposeListener(this.listWidgetDisposeListener); + + this.synchronizeListWidget(); + } + + + // ********** initialization ********** + + private ListChangeListener buildListChangeListener() { + return new SWTListChangeListenerWrapper(this.buildListChangeListener_()); + } + + private ListChangeListener buildListChangeListener_() { + return new ListChangeListener() { + public void itemsAdded(ListAddEvent event) { + ListWidgetModelBinding.this.listItemsAdded(event); + } + public void itemsRemoved(ListRemoveEvent event) { + ListWidgetModelBinding.this.listItemsRemoved(event); + } + public void itemsMoved(ListMoveEvent event) { + ListWidgetModelBinding.this.listItemsMoved(event); + } + public void itemsReplaced(ListReplaceEvent event) { + ListWidgetModelBinding.this.listItemsReplaced(event); + } + public void listCleared(ListClearEvent event) { + ListWidgetModelBinding.this.listCleared(event); + } + public void listChanged(ListChangeEvent event) { + ListWidgetModelBinding.this.listChanged(event); + } + @Override + public String toString() { + return "list listener"; + } + }; + } + + private DisposeListener buildListWidgetDisposeListener() { + return new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + ListWidgetModelBinding.this.listWidgetDisposed(event); + } + @Override + public String toString() { + return "list widget dispose listener"; + } + }; + } + + + // ********** list ********** + + /** + * Brute force synchronization of list widget with the model list. + */ + private void synchronizeListWidget() { + if ( ! this.listWidget.isDisposed()) { + this.synchronizeListWidget_(); + } + } + + private void synchronizeListWidget_() { + ArrayList<String> items = new ArrayList<String>(this.listModel.size()); + for (E item : this.listModel) { + items.add(this.convert(item)); + } + this.listWidget.setItems(items.toArray(new String[items.size()])); + + // now that the list has changed, we need to synch the selection + this.selectionBinding.synchronizeListWidgetSelection(); + } + + /** + * The model has changed - synchronize the list widget. + */ + void listItemsAdded(ListAddEvent event) { + if ( ! this.listWidget.isDisposed()) { + this.listItemsAdded_(event); + } + } + + private void listItemsAdded_(ListAddEvent event) { + int i = event.getIndex(); + for (E item : this.getItems(event)) { + this.listWidget.add(this.convert(item), i++); + } + + // now that the list has changed, we need to synch the selection + this.selectionBinding.synchronizeListWidgetSelection(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + private Iterable<E> getItems(ListAddEvent event) { + return (Iterable<E>) event.getItems(); + } + + /** + * The model has changed - synchronize the list widget. + */ + void listItemsRemoved(ListRemoveEvent event) { + if ( ! this.listWidget.isDisposed()) { + this.listItemsRemoved_(event); + } + } + + private void listItemsRemoved_(ListRemoveEvent event) { + this.listWidget.remove(event.getIndex(), event.getIndex() + event.getItemsSize() - 1); + + // now that the list has changed, we need to synch the selection + this.selectionBinding.synchronizeListWidgetSelection(); + } + + /** + * The model has changed - synchronize the list widget. + */ + void listItemsMoved(ListMoveEvent event) { + if ( ! this.listWidget.isDisposed()) { + this.listItemsMoved_(event); + } + } + + private void listItemsMoved_(ListMoveEvent event) { + int target = event.getTargetIndex(); + int source = event.getSourceIndex(); + int len = event.getLength(); + int loStart = Math.min(target, source); + int hiStart = Math.max(target, source); + // make a copy of the affected items... + String[] subArray = ArrayTools.subArray(this.listWidget.getItems(), loStart, hiStart + len); + // ...move them around... + subArray = ArrayTools.move(subArray, target - loStart, source - loStart, len); + // ...and then put them back + int i = loStart; + for (String item : subArray) { + this.listWidget.setItem(i++, item); + } + + // now that the list has changed, we need to synch the selection + this.selectionBinding.synchronizeListWidgetSelection(); + } + + /** + * The model has changed - synchronize the list widget. + */ + void listItemsReplaced(ListReplaceEvent event) { + if ( ! this.listWidget.isDisposed()) { + this.listItemsReplaced_(event); + } + } + + private void listItemsReplaced_(ListReplaceEvent event) { + int i = event.getIndex(); + for (E item : this.getNewItems(event)) { + this.listWidget.setItem(i++, this.convert(item)); + } + + // now that the list has changed, we need to synch the selection + this.selectionBinding.synchronizeListWidgetSelection(); + } + + // minimized scope of suppressed warnings + @SuppressWarnings("unchecked") + private Iterable<E> getNewItems(ListReplaceEvent event) { + return (Iterable<E>) event.getNewItems(); + } + + /** + * The model has changed - synchronize the list widget. + */ + void listCleared(ListClearEvent event) { + if ( ! this.listWidget.isDisposed()) { + this.listCleared_(event); + } + } + + private void listCleared_(@SuppressWarnings("unused") ListClearEvent event) { + this.listWidget.removeAll(); + } + + /** + * The model has changed - synchronize the list widget. + */ + void listChanged(ListChangeEvent event) { + if ( ! this.listWidget.isDisposed()) { + this.listChanged_(event); + } + } + + private void listChanged_(@SuppressWarnings("unused") ListChangeEvent event) { + this.synchronizeListWidget_(); + } + + /** + * Use the string converter to convert the specified item to a + * string that can be added to the list widget. + */ + private String convert(E item) { + return this.stringConverter.convertToString(item); + } + + + // ********** list widget events ********** + + void listWidgetDisposed(@SuppressWarnings("unused") DisposeEvent event) { + // the list widget is not yet "disposed" when we receive this event + // so we can still remove our listeners + this.listWidget.removeDisposeListener(this.listWidgetDisposeListener); + this.listModel.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener); + this.selectionBinding.dispose(); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.listModel); + } + + + // ********** adapter interfaces ********** + + /** + * Adapter used by the list widget model binding to query and manipulate + * the widget. + */ + interface ListWidget { + + /** + * Return whether the list widget is "disposed". + */ + boolean isDisposed(); + + /** + * Add the specified dispose listener to the list widget. + */ + void addDisposeListener(DisposeListener listener); + + /** + * Remove the specified dispose listener from the list widget. + */ + void removeDisposeListener(DisposeListener listener); + + /** + * Return the list widget's items. + */ + String[] getItems(); + + /** + * Set the list widget's item at the specified index to the specified item. + */ + void setItem(int index, String item); + + /** + * Set the list widget's items. + */ + void setItems(String[] items); + + /** + * Add the specified item to the list widget's items at the specified index. + */ + void add(String item, int index); + + /** + * Remove the specified range of items from the list widget's items. + */ + void remove(int start, int end); + + /** + * Remove all the items from the list widget. + */ + void removeAll(); + + } + + + /** + * Widget-specific selection binding that is controlled by the list widget + * model binding. + */ + interface SelectionBinding { + + /** + * Synchronize the selection binding's widget with the selection model. + * <p> + * Pre-condition: The widget is not disposed. + */ + void synchronizeListWidgetSelection(); + + /** + * The widget has been disposed; dispose the selection binding. + */ + void dispose(); + + + /** + * Useful for list boxes that ignore the selection. + */ + final class Null implements SelectionBinding { + public static final SelectionBinding INSTANCE = new Null(); + public static SelectionBinding instance() { + return INSTANCE; + } + // ensure single instance + private Null() { + super(); + } + public void synchronizeListWidgetSelection() { + // do nothing + } + public void dispose() { + // do nothing + } + @Override + public String toString() { + return "SelectionBinding.Null"; //$NON-NLS-1$ + } + } + + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/MultiControlBooleanStateController.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/MultiControlBooleanStateController.java new file mode 100644 index 0000000000..304b78ab6d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/MultiControlBooleanStateController.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import java.util.HashSet; + +import org.eclipse.jpt.utility.internal.iterables.SnapshotCloneIterable; +import org.eclipse.jpt.utility.model.event.CollectionAddEvent; +import org.eclipse.jpt.utility.model.event.CollectionChangeEvent; +import org.eclipse.jpt.utility.model.event.CollectionClearEvent; +import org.eclipse.jpt.utility.model.event.CollectionRemoveEvent; +import org.eclipse.jpt.utility.model.listener.CollectionChangeListener; +import org.eclipse.jpt.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.widgets.Control; + +/** + * This controller enables a boolean model to control either the + * <em>enabled</em> or <em>visible</em> properties of a set of SWT controls; + * i.e. the controls' properties are kept in synch with the boolean model, + * but <em>not</em> vice-versa. + * + * @see PropertyValueModel + * @see CollectionValueModel + * @see Control#setEnabled(boolean) + * @see Control#setVisible(boolean) + */ +final class MultiControlBooleanStateController + extends BooleanStateController +{ + /** + * The set of controls whose state is kept in sync with the boolean model. + */ + private final CollectionValueModel<? extends Control> controlsModel; + + /** + * A listener that allows clients to add/remove controls. + */ + private final CollectionChangeListener controlsListener; + + /** + * Cache of controls. + */ + private final HashSet<Control> controls = new HashSet<Control>(); + + + // ********** constructor ********** + + /** + * Constructor - the boolean model, the controls model, and the adapter are required. + */ + MultiControlBooleanStateController( + PropertyValueModel<Boolean> booleanModel, + CollectionValueModel<? extends Control> controlsModel, + boolean defaultValue, + Adapter adapter + ) { + super(booleanModel, defaultValue, adapter); + if (controlsModel == null) { + throw new NullPointerException(); + } + this.controlsModel = controlsModel; + this.controlsListener = this.buildControlsListener(); + this.addControls(controlsModel); + } + + + // ********** initialization ********** + + private CollectionChangeListener buildControlsListener() { + return new CollectionChangeListener() { + @SuppressWarnings("unchecked") + public void itemsAdded(CollectionAddEvent event) { + MultiControlBooleanStateController.this.addControls((Iterable<? extends Control>) event.getItems()); + } + @SuppressWarnings("unchecked") + public void itemsRemoved(CollectionRemoveEvent event) { + MultiControlBooleanStateController.this.removeControls((Iterable<? extends Control>) event.getItems()); + } + public void collectionCleared(CollectionClearEvent event) { + MultiControlBooleanStateController.this.clearControls(); + } + @SuppressWarnings("unchecked") + public void collectionChanged(CollectionChangeEvent event) { + MultiControlBooleanStateController.this.clearControls(); + MultiControlBooleanStateController.this.addControls((Iterable<? extends Control>) event.getCollection()); + } + @Override + public String toString() { + return "controls listener"; //$NON-NLS-1$ + } + }; + } + + + // ********** controls ********** + + @Override + void setControlState(boolean b) { + for (Control control : this.controls) { + this.setControlState(control, b); + } + } + + /* CU private */ void addControls(Iterable<? extends Control> controls_) { + boolean b = this.getBooleanValue(); + for (Control control : controls_) { + this.addControl(control, b); + } + } + + private void addControl(Control control, boolean b) { + if (this.controls.isEmpty()) { + this.engageBooleanModel(); + this.controlsModel.addCollectionChangeListener(CollectionValueModel.VALUES, this.controlsListener); + } + if (this.controls.add(control)) { + this.engageControl(control); + this.setControlState(control, b); + } else { + throw new IllegalArgumentException("duplicate control: " + control); //$NON-NLS-1$ + } + } + + /* CU private */ void clearControls() { + this.removeControls(new SnapshotCloneIterable<Control>(this.controls)); + } + + /* CU private */ void removeControls(Iterable<? extends Control> controls_) { + for (Control control : controls_) { + this.disengageControl(control); + this.removeControl(control); + } + } + + private void removeControl(Control control) { + this.controls.remove(control); + if (this.controls.isEmpty()) { + this.controlsModel.removeCollectionChangeListener(CollectionValueModel.VALUES, this.controlsListener); + this.disengageBooleanModel(); + } + } + + @Override + void controlDisposed(Control control) { + super.controlDisposed(control); + this.removeControl(control); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTComboAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTComboAdapter.java new file mode 100644 index 0000000000..672524d2d2 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTComboAdapter.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Combo; + +/** + * Adapt an SWT {@link Combo} to the list widget expected by + * {@link ListWidgetModelBinding} and the + * drop-down list box expected by {@link DropDownListBoxSelectionBinding}. + */ +final class SWTComboAdapter + extends AbstractListWidgetAdapter<Combo> + implements DropDownListBoxSelectionBinding.DropDownListBox +{ + SWTComboAdapter(Combo combo) { + super(combo); + } + + // ********** ListWidgetModelBinding.ListWidget implementation ********** + public String[] getItems() { + return this.widget.getItems(); + } + public void setItem(int index, String item) { + this.widget.setItem(index, item); + } + public void setItems(String[] items) { + this.widget.setItems(items); + } + public void add(String item, int index) { + this.widget.add(item, index); + } + public void remove(int start, int end) { + this.widget.remove(start, end); + } + public void removeAll() { + this.widget.removeAll(); + } + + // ********** ComboBoxSelectionBinding.ComboBox implementation ********** + public void addSelectionListener(SelectionListener listener) { + this.widget.addSelectionListener(listener); + } + public void removeSelectionListener(SelectionListener listener) { + this.widget.removeSelectionListener(listener); + } + public int getSelectionIndex() { + return this.widget.getSelectionIndex(); + } + public void select(int index) { + this.widget.select(index); + } + public void deselect(int index) { + this.widget.deselect(index); + } + public void deselectAll() { + this.widget.deselectAll(); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTListAdapter.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTListAdapter.java new file mode 100644 index 0000000000..767be1bb86 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTListAdapter.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.List; + +/** + * Adapt an SWT {@link List} to the list widget expected by + * {@link ListWidgetModelBinding}. + */ +final class SWTListAdapter + extends AbstractListWidgetAdapter<List> +{ + SWTListAdapter(List list) { + super(list); + } + public void addSelectionListener(SelectionListener listener) { + this.widget.addSelectionListener(listener); + } + public void removeSelectionListener(SelectionListener listener) { + this.widget.removeSelectionListener(listener); + } + public String[] getItems() { + return this.widget.getItems(); + } + public void setItem(int index, String item) { + this.widget.setItem(index, item); + } + public void setItems(String[] items) { + this.widget.setItems(items); + } + public void add(String item, int index) { + this.widget.add(item, index); + } + public void remove(int start, int end) { + this.widget.remove(start, end); + } + public void removeAll() { + this.widget.removeAll(); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTTools.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTTools.java new file mode 100644 index 0000000000..9b0f266f9f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTTools.java @@ -0,0 +1,392 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import java.util.Arrays; + +import org.eclipse.jpt.utility.internal.BitTools; +import org.eclipse.jpt.utility.internal.StringConverter; +import org.eclipse.jpt.utility.internal.model.value.StaticCollectionValueModel; +import org.eclipse.jpt.utility.internal.model.value.WritablePropertyCollectionValueModelAdapter; +import org.eclipse.jpt.utility.model.value.CollectionValueModel; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritableCollectionValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.List; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Widget; + +/** + * Various SWT tools. + */ +@SuppressWarnings("nls") +public final class SWTTools { + + // ********** check-box/radio button/toggle button ********** + + /** + * Bind the specified button (check-box, radio button, or toggle button) + * to the specified boolean model. + * If the boolean model is <code>null<code>, the button's 'selection' state will + * be <code>false<code>. + */ + public static void bind(WritablePropertyValueModel<Boolean> booleanModel, Button button) { + bind(booleanModel, button, false); + } + + /** + * Bind the specified button (check-box, radio button, or toggle button) + * to the specified boolean model. + * If the boolean model is <code>null<code>, the button's 'selection' state will + * be the specified default value. + */ + public static void bind(WritablePropertyValueModel<Boolean> booleanModel, Button button, boolean defaultValue) { + // the new binding will add itself as a listener to the boolean model and the button + new BooleanButtonModelBinding(booleanModel, button, defaultValue); + } + + + // ********** text field ********** + + /** + * Bind the specified text model to the specified text field. + */ + public static <E> void bind(WritablePropertyValueModel<String> textModel, Text textField) { + // the new binding will add itself as a listener to the text model and the text field + new TextFieldModelBinding(textModel, textField); + } + + + // ********** list box ********** + + /** + * Bind the specified model list to the specified list box. + * The list box selection is ignored. + * Use the default string converter to convert the model items to strings + * to be displayed in the list box, which calls {@link Object#toString()} + * on the items in the model list. + */ + public static <E> void bind(ListValueModel<E> listModel, List listBox) { + bind(listModel, listBox, StringConverter.Default.<E>instance()); + } + + /** + * Bind the specified model list to the specified list box. + * The list box selection is ignored. + * Use the specified string converter to convert the model items to strings + * to be displayed in the list box. + */ + public static <E> void bind(ListValueModel<E> listModel, List listBox, StringConverter<E> stringConverter) { + bind(listModel, new SWTListAdapter(listBox), stringConverter); + } + + /** + * Bind the specified model list and selection to the specified list box. + * Use the default string converter to convert the model items to strings + * to be displayed in the list box, which calls {@link Object#toString()} + * on the items in the model list. + */ + public static <E> void bind(ListValueModel<E> listModel, WritablePropertyValueModel<E> selectedItemModel, List listBox) { + bind(listModel, selectedItemModel, listBox, StringConverter.Default.<E>instance()); + } + + /** + * Adapt the specified model list and selection to the specified list box. + * Use the specified string converter to convert the model items to strings + * to be displayed in the list box. + */ + public static <E> void bind(ListValueModel<E> listModel, WritablePropertyValueModel<E> selectedItemModel, List listBox, StringConverter<E> stringConverter) { + checkForSingleSelectionStyle(listBox); + bind(listModel, new WritablePropertyCollectionValueModelAdapter<E>(selectedItemModel), listBox, stringConverter); + } + + /** + * Bind the specified model list and selections to the specified list box. + * Use the default string converter to convert the model items to strings + * to be displayed in the list box, which calls {@link Object#toString()} + * on the items in the model list. + */ + public static <E> void bind(ListValueModel<E> listModel, WritableCollectionValueModel<E> selectedItemsModel, List listBox) { + bind(listModel, selectedItemsModel, listBox, StringConverter.Default.<E>instance()); + } + + /** + * Bind the specified model list and selections to the specified list box. + * Use the specified string converter to convert the model items to strings + * to be displayed in the list box. + */ + public static <E> void bind(ListValueModel<E> listModel, WritableCollectionValueModel<E> selectedItemsModel, List listBox, StringConverter<E> stringConverter) { + bind( + listModel, + new SWTListAdapter(listBox), + stringConverter, + new ListBoxSelectionBinding<E>(listModel, selectedItemsModel, listBox) + ); + } + + private static void checkForSingleSelectionStyle(List listBox) { + if ( ! BitTools.flagIsSet(listBox.getStyle(), SWT.SINGLE)) { + throw new IllegalStateException("list box must be single-selection: " + listBox); + } + } + + + // ********** drop-down list box ********** + + /** + * Bind the specified model list and selection to the specified drop-down list box. + * Use the default string converter to convert the model items to strings + * to be displayed in the drop-down list box, which calls {@link Object#toString()} + * on the items in the model list. + */ + public static <E> void bind(ListValueModel<E> listModel, WritablePropertyValueModel<E> selectedItemModel, Combo dropDownListBox) { + bind(listModel, selectedItemModel, dropDownListBox, StringConverter.Default.<E>instance()); + } + + /** + * Adapt the specified model list and selection to the specified drop-down list box. + * Use the specified string converter to convert the model items to strings + * to be displayed in the drop-down list box. + */ + public static <E> void bind(ListValueModel<E> listModel, WritablePropertyValueModel<E> selectedItemModel, Combo dropDownListBox, StringConverter<E> stringConverter) { + checkForReadOnlyStyle(dropDownListBox); + SWTComboAdapter comboAdapter = new SWTComboAdapter(dropDownListBox); + bind( + listModel, + comboAdapter, + stringConverter, + new DropDownListBoxSelectionBinding<E>(listModel, selectedItemModel, comboAdapter) + ); + } + + private static void checkForReadOnlyStyle(Widget comboBox) { + if ( ! BitTools.flagIsSet(comboBox.getStyle(), SWT.READ_ONLY)) { + throw new IllegalStateException("combo-box must be read-only: " + comboBox); + } + } + + + // ********** list "widget" ********** + + /** + * Bind the specified model list to the specified list widget. + * The list widget's selection is ignored. + * Use the specified string converter to convert the model items to strings + * to be displayed in the list box. + */ + private static <E> void bind(ListValueModel<E> listModel, ListWidgetModelBinding.ListWidget listWidget, StringConverter<E> stringConverter) { + bind(listModel, listWidget, stringConverter, ListWidgetModelBinding.SelectionBinding.Null.instance()); + } + + /** + * Bind the specified model list to the specified list widget. + * Use the specified selection binding to control the list widget's selection. + * Use the specified string converter to convert the model items to strings + * to be displayed in the list box. + */ + private static <E> void bind(ListValueModel<E> listModel, ListWidgetModelBinding.ListWidget listWidget, StringConverter<E> stringConverter, ListWidgetModelBinding.SelectionBinding selectionBinding) { + // the new binding will add itself as a listener to the value models and the list box + new ListWidgetModelBinding<E>(listModel, listWidget, stringConverter, selectionBinding); + } + + + // ********** 'enabled' state ********** + + /** + * Control the <em>enabled</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>enabled</em> states will be <code>false<code>. + */ + public static void controlEnabledState(PropertyValueModel<Boolean> booleanModel, Control... controls) { + controlEnabledState(booleanModel, controls, false); + } + + /** + * Control the <em>enabled</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>enabled</em> states will be the specified default value. + */ + public static void controlEnabledState(PropertyValueModel<Boolean> booleanModel, Control[] controls, boolean defaultValue) { + switch (controls.length) { + case 0: + throw new IllegalArgumentException("empty controls array: " + Arrays.toString(controls)); + case 1: + controlEnabledState(booleanModel, controls[0], defaultValue); + break; + default: + controlEnabledState(booleanModel, new StaticCollectionValueModel<Control>(controls), defaultValue); + break; + } + } + + /** + * Control the <em>enabled</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>enabled</em> states will be <code>false<code>. + */ + public static void controlEnabledState(PropertyValueModel<Boolean> booleanModel, Iterable<? extends Control> controls) { + controlEnabledState(booleanModel, controls, false); + } + + /** + * Control the <em>enabled</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>enabled</em> states will be the specified default value. + */ + public static void controlEnabledState(PropertyValueModel<Boolean> booleanModel, Iterable<? extends Control> controls, boolean defaultValue) { + controlEnabledState(booleanModel, new StaticCollectionValueModel<Control>(controls), defaultValue); + } + + /** + * Control the <em>enabled</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>enabled</em> states will be <code>false<code>. + */ + public static void controlEnabledState(PropertyValueModel<Boolean> booleanModel, CollectionValueModel<? extends Control> controlsModel) { + controlEnabledState(booleanModel, controlsModel, false); + } + + /** + * Control the <em>enabled</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>enabled</em> states will be the specified default value. + */ + public static void controlEnabledState(PropertyValueModel<Boolean> booleanModel, CollectionValueModel<? extends Control> controlsModel, boolean defaultValue) { + control(booleanModel, controlsModel, defaultValue, ENABLED_ADAPTER); + } + + /** + * Control the <em>enabled</em> state of the specified control with the + * specified boolean. If the boolean is <code>null<code>, the control's + * <em>enabled</em> state will be the specified default value. + */ + public static void controlEnabledState(PropertyValueModel<Boolean> booleanModel, Control control, boolean defaultValue) { + control(booleanModel, control, defaultValue, ENABLED_ADAPTER); + } + + private static final BooleanStateController.Adapter ENABLED_ADAPTER = + new BooleanStateController.Adapter() { + public void setState(Control control, boolean b) { + control.setEnabled(b); + } + }; + + + // ********** 'visible' state ********** + + /** + * Control the <em>visible</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>visible</em> states will be <code>false<code>. + */ + public static void controlVisibleState(PropertyValueModel<Boolean> booleanModel, Control... controls) { + controlVisibleState(booleanModel, controls, false); + } + + /** + * Control the <em>visible</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>visible</em> states will be the specified default value. + */ + public static void controlVisibleState(PropertyValueModel<Boolean> booleanModel, Control[] controls, boolean defaultValue) { + switch (controls.length) { + case 0: + throw new IllegalArgumentException("empty controls array: " + Arrays.toString(controls)); + case 1: + controlVisibleState(booleanModel, controls[0], defaultValue); + break; + default: + controlVisibleState(booleanModel, new StaticCollectionValueModel<Control>(controls), defaultValue); + break; + } + } + + /** + * Control the <em>visible</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>visible</em> states will be <code>false<code>. + */ + public static void controlVisibleState(PropertyValueModel<Boolean> booleanModel, Iterable<? extends Control> controls) { + controlVisibleState(booleanModel, controls, false); + } + + /** + * Control the <em>visible</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>visible</em> states will be the specified default value. + */ + public static void controlVisibleState(PropertyValueModel<Boolean> booleanModel, Iterable<? extends Control> controls, boolean defaultValue) { + controlVisibleState(booleanModel, new StaticCollectionValueModel<Control>(controls), defaultValue); + } + + /** + * Control the <em>visible</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>visible</em> states will be <code>false<code>. + */ + public static void controlVisibleState(PropertyValueModel<Boolean> booleanModel, CollectionValueModel<? extends Control> controlsModel) { + controlVisibleState(booleanModel, controlsModel, false); + } + + /** + * Control the <em>visible</em> state of the specified controls with the + * specified boolean. If the boolean is <code>null<code>, the controls' + * <em>visible</em> states will be the specified default value. + */ + public static void controlVisibleState(PropertyValueModel<Boolean> booleanModel, CollectionValueModel<? extends Control> controlsModel, boolean defaultValue) { + control(booleanModel, controlsModel, defaultValue, VISIBLE_ADAPTER); + } + + /** + * Control the <em>visible</em> state of the specified control with the + * specified boolean. If the boolean is <code>null<code>, the control's + * <em>visible</em> state will be the specified default value. + */ + public static void controlVisibleState(PropertyValueModel<Boolean> booleanModel, Control control, boolean defaultValue) { + control(booleanModel, control, defaultValue, VISIBLE_ADAPTER); + } + + private static final BooleanStateController.Adapter VISIBLE_ADAPTER = + new BooleanStateController.Adapter() { + public void setState(Control control, boolean b) { + control.setVisible(b); + } + }; + + + // ********** boolean state controller ********** + + private static void control(PropertyValueModel<Boolean> booleanModel, CollectionValueModel<? extends Control> controlsModel, boolean defaultValue, BooleanStateController.Adapter adapter) { + // the new controller will add itself as a listener to the value model and the controls + new MultiControlBooleanStateController(booleanModel, controlsModel, defaultValue, adapter); + } + + private static void control(PropertyValueModel<Boolean> booleanModel, Control control, boolean defaultValue, BooleanStateController.Adapter adapter) { + // the new controller will add itself as a listener to the value model and the controls + new SimpleBooleanStateController(booleanModel, control, defaultValue, adapter); + } + + + // ********** constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private SWTTools() { + super(); + throw new UnsupportedOperationException(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SimpleBooleanStateController.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SimpleBooleanStateController.java new file mode 100644 index 0000000000..4530f9eebe --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SimpleBooleanStateController.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.widgets.Control; + +/** + * This controller enables a boolean model to control either the + * <em>enabled</em> or <em>visible</em> properties of an SWT control; i.e. the + * control's property is kept in synch with the boolean model, + * but <em>not</em> vice-versa. + * <p> + * Once the control is disposed, this controller is kaput. + * + * @see PropertyValueModel + * @see Control#setEnabled(boolean) + * @see Control#setVisible(boolean) + */ +final class SimpleBooleanStateController + extends BooleanStateController +{ + private final Control control; + + + // ********** constructor ********** + + /** + * Constructor - the boolean model, the control, and the adapter are required. + */ + SimpleBooleanStateController( + PropertyValueModel<Boolean> booleanModel, + Control control, + boolean defaultValue, + Adapter adapter + ) { + super(booleanModel, defaultValue, adapter); + if (control == null) { + throw new NullPointerException(); + } + this.control = control; + this.engageBooleanModel(); + this.engageControl(control); + this.setControlState(control, this.getBooleanValue()); + } + + + // ********** controls ********** + + @Override + void setControlState(boolean b) { + this.setControlState(this.control, b); + } + + @Override + void controlDisposed(Control c) { + super.controlDisposed(c); + this.disengageBooleanModel(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/TextFieldModelBinding.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/TextFieldModelBinding.java new file mode 100644 index 0000000000..ea60f2fd1f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/TextFieldModelBinding.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.utility.swt; + +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.widgets.Text; + +/** + * This binding can be used to keep a text field + * synchronized with a model text/string. + * + * @see WritablePropertyValueModel + * @see Text + */ +@SuppressWarnings("nls") +class TextFieldModelBinding { + + /** + * The text model we keep synchronized with the text field. + */ + private final WritablePropertyValueModel<String> textModel; + + /** + * A listener that allows us to synchronize the text field's contents with + * the text model. + */ + private final PropertyChangeListener textModelChangeListener; + + /** + * The text field we keep synchronized with the text model. + */ + private final Text textField; + + /** + * A listener that allows us to synchronize our text model + * with the text field's contents. + */ + private final ModifyListener textFieldModifyListener; + + /** + * A listener that allows us to stop listening to stuff when the text field + * is disposed. + */ + private final DisposeListener textFieldDisposeListener; + + /** + * Hmm... + */ + private boolean settingTextFieldText = false; + + + // ********** constructor ********** + + /** + * Constructor - the text model and text field are required. + */ + TextFieldModelBinding(WritablePropertyValueModel<String> textModel, Text textField) { + super(); + if ((textModel == null) || (textField == null)) { + throw new NullPointerException(); + } + this.textModel = textModel; + this.textField = textField; + + this.textModelChangeListener = this.buildTextModelChangeListener(); + this.textModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.textModelChangeListener); + + this.textFieldModifyListener = this.buildTextFieldModifyListener(); + this.textField.addModifyListener(this.textFieldModifyListener); + + this.textFieldDisposeListener = this.buildTextFieldDisposeListener(); + this.textField.addDisposeListener(this.textFieldDisposeListener); + + this.setTextFieldText(textModel.getValue()); + } + + + // ********** initialization ********** + + private PropertyChangeListener buildTextModelChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildTextModelChangeListener_()); + } + + private PropertyChangeListener buildTextModelChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent event) { + TextFieldModelBinding.this.textModelChanged(event); + } + @Override + public String toString() { + return "text listener"; + } + }; + } + + private ModifyListener buildTextFieldModifyListener() { + return new ModifyListener() { + public void modifyText(ModifyEvent event) { + TextFieldModelBinding.this.textFieldModified(); + } + @Override + public String toString() { + return "text field modify listener"; + } + }; + } + + private DisposeListener buildTextFieldDisposeListener() { + return new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + TextFieldModelBinding.this.textFieldDisposed(); + } + @Override + public String toString() { + return "text field dispose listener"; + } + }; + } + + + // ********** text model events ********** + + /* CU private */ void textModelChanged(PropertyChangeEvent event) { + if ( ! this.textField.isDisposed()) { // ??? + this.setTextFieldText((String) event.getNewValue()); + } + } + + private void setTextFieldText(String text) { + // the text model can be null, but the text field cannot + this.setTextFieldText_((text == null) ? "" : text); + } + + private void setTextFieldText_(String text) { + if ( ! text.equals(this.textField.getText())) { // ??? + this.setTextFieldText__(text); + } + } + + private void setTextFieldText__(String text) { + this.settingTextFieldText = true; + try { + this.textField.setText(text); + } finally { + this.settingTextFieldText = false; + } + } + + + // ********** text field events ********** + + /* CU private */ void textFieldModified() { + if ( ! this.settingTextFieldText) { + this.setTextModelText(this.textField.getText()); + } + } + + private void setTextModelText(String text) { + if ( ! text.equals(this.textModel.getValue())) { // ??? + this.textModel.setValue(text); + } + } + + /* CU private */ void textFieldDisposed() { + // the text field is not yet "disposed" when we receive this event + // so we can still remove our listeners + this.textField.removeDisposeListener(this.textFieldDisposeListener); + this.textField.removeModifyListener(this.textFieldModifyListener); + this.textModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.textModelChangeListener); + } + + + // ********** standard methods ********** + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.textModel); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemoveListPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemoveListPane.java new file mode 100644 index 0000000000..8b06cae4f4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemoveListPane.java @@ -0,0 +1,554 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.ui.internal.swt.ColumnAdapter; +import org.eclipse.jpt.common.ui.internal.swt.TableModelAdapter; +import org.eclipse.jpt.common.ui.internal.swt.TableModelAdapter.SelectionChangeEvent; +import org.eclipse.jpt.common.ui.internal.swt.TableModelAdapter.SelectionChangeListener; +import org.eclipse.jpt.common.ui.internal.util.SWTUtil; +import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel; +import org.eclipse.jpt.utility.internal.model.value.swing.ObjectListSelectionModel; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.event.ListAddEvent; +import org.eclipse.jpt.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.utility.model.event.ListClearEvent; +import org.eclipse.jpt.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; + +/** + * This implementation of the <code>AddRemovePane</code> uses a <code>Table</code> + * as its main widget, a <code>List</code> can't be used because it doesn't + * support showing images. However, the table is displayed like a list. + * <p> + * Here the layot of this pane: + * <pre> + * ----------------------------------------------------------------------------- + * | ------------------------------------------------------------- ----------- | + * | | Item 1 | | Add... | | + * | | ... | ----------- | + * | | Item n | ----------- | + * | | | | Edit... | | + * | | | ----------- | + * | | | ----------- | + * | | | | Remove | | + * | | | ----------- | + * | ------------------------------------------------------------- | + * -----------------------------------------------------------------------------</pre> + * + * @version 2.0 + * @since 1.0 + */ +@SuppressWarnings("nls") +public class AddRemoveListPane<T extends Model> extends AddRemovePane<T> +{ + + /** + * The main widget of this add/remove pane. + */ + private Table table; + + /** + * Creates a new <code>AddRemoveListPane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param adapter + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the table holder's items + */ + public AddRemoveListPane(Pane<? extends T> parentPane, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + ILabelProvider labelProvider) { + + super(parentPane, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider); + } + + /** + * Creates a new <code>AddRemoveListPane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param adapter + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the table holder's items + * @param helpId The topic help ID to be registered with this pane + */ + public AddRemoveListPane(Pane<? extends T> parentPane, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + ILabelProvider labelProvider, + String helpId) { + + super(parentPane, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider, + helpId); + } + + /** + * Creates a new <code>AddRemoveListPane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param adapter + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the table holder's items + * @param helpId The topic help ID to be registered with this pane + * @param parentManagePane <code>true</code> to have the parent pane manage + * the enabled state of this pane + */ + public AddRemoveListPane(Pane<? extends T> parentPane, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + ILabelProvider labelProvider, + String helpId, + boolean parentManagePane) { + + super(parentPane, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider, + helpId, + parentManagePane); + } + + /** + * Creates a new <code>AddRemoveListPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of the subject + * @param adapter + * @param parent The parent container + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the table holder's items + */ + public AddRemoveListPane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + ILabelProvider labelProvider) { + + super(parentPane, + subjectHolder, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider); + } + + /** + * Creates a new <code>AddRemoveListPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of the subject + * @param adapter + * @param parent The parent container + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the table holder's items + * @param helpId The topic help ID to be registered with this pane + */ + public AddRemoveListPane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + ILabelProvider labelProvider, + String helpId) { + + super(parentPane, + subjectHolder, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider, + helpId); + } + + private ColumnAdapter<Object> buildColumnAdapter() { + return new ColumnAdapter<Object>() { + public WritablePropertyValueModel<?>[] cellModels(Object subject) { + WritablePropertyValueModel<?>[] valueHolders = new WritablePropertyValueModel<?>[1]; + valueHolders[0] = new SimplePropertyValueModel<Object>(subject); + return valueHolders; + } + + public int columnCount() { + return 1; + } + + public String columnName(int columnIndex) { + return ""; + } + }; + } + + @Override + protected void itemsAdded(ListAddEvent e) { + super.itemsAdded(e); + revalidateLayout(); + } + + @Override + protected void itemsMoved(ListMoveEvent e) { + super.itemsMoved(e); + revalidateLayout(); + } + + @Override + protected void itemsRemoved(ListRemoveEvent e) { + super.itemsRemoved(e); + revalidateLayout(); + } + + @Override + protected void itemsReplaced(ListReplaceEvent e) { + super.itemsReplaced(e); + revalidateLayout(); + } + + @Override + protected void listChanged(ListChangeEvent e) { + super.listChanged(e); + revalidateLayout(); + } + + @Override + protected void listCleared(ListClearEvent e) { + super.listCleared(e); + revalidateLayout(); + } + + /** + * Revalidates the table layout after the list of items has changed. The + * layout has to be done in a new UI thread because our listener might be + * notified before the table has been updated (table column added or removed). + */ + private void revalidateLayout() { + SWTUtil.asyncExec(new Runnable() { public void run() { + if (!table.isDisposed()) { + table.getParent().computeSize(SWT.DEFAULT, SWT.DEFAULT); + table.getParent().layout(); + } + }}); + } + + private PropertyChangeListener buildSelectedItemPropertyChangeListener() { + return new SWTPropertyChangeListenerWrapper( + buildSelectedItemPropertyChangeListener_() + ); + } + + private PropertyChangeListener buildSelectedItemPropertyChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent e) { + if (table.isDisposed()) { + return; + } + getSelectionModel().setSelectedValue(e.getNewValue()); + updateButtons(); + } + }; + } + + private SelectionChangeListener<Object> buildSelectionListener() { + return new SelectionChangeListener<Object>() { + public void selectionChanged(SelectionChangeEvent<Object> e) { + AddRemoveListPane.this.selectionChanged(); + } + }; + } + + private Composite addTableContainer(Composite container) { + + container = addPane(container, buildTableContainerLayout()); + container.setLayoutData(new GridData(GridData.FILL_BOTH)); + return container; + } + + private Layout buildTableContainerLayout() { + return new Layout() { + @Override + protected Point computeSize(Composite composite, + int widthHint, + int heightHint, + boolean flushCache) { + + Table table = (Table) composite.getChildren()[0]; + TableColumn tableColumn = table.getColumn(0); + int columnWidth = tableColumn.getWidth(); + packColumn(table); + + // Calculate the table size and adjust it with the hints + Point size = table.computeSize(SWT.DEFAULT, SWT.DEFAULT); + + if (widthHint != SWT.DEFAULT) { + size.x = widthHint; + } + + if (heightHint != SWT.DEFAULT) { + size.y = heightHint; + } + + // Revert the column's width to its current value + table.setRedraw(false); + table.setLayoutDeferred(true); + tableColumn.setWidth(columnWidth); + table.setLayoutDeferred(false); + table.setRedraw(true); + + return size; + } + + private boolean isVerticalScrollbarBarVisible(Table table, + Rectangle clientArea) { + + // Get the height of all the rows + int height = table.getItemCount() * table.getItemHeight(); + + // Remove the border from the height + height += (table.getBorderWidth() * 2); + + return (clientArea.height < height); + } + + @Override + protected void layout(Composite composite, boolean flushCache) { + + Rectangle bounds = composite.getClientArea(); + + if (bounds.width > 0) { + + Table table = (Table) composite.getChildren()[0]; + table.setBounds(0, 0, bounds.width, bounds.height); + + updateTableColumnWidth( + table, + bounds.width, + isVerticalScrollbarBarVisible(table, bounds) + ); + } + } + + private void packColumn(Table table) { + + TableColumn tableColumn = table.getColumn(0); + + table.setRedraw(false); + table.setLayoutDeferred(true); + tableColumn.pack(); + table.setLayoutDeferred(false); + table.setRedraw(true); + + // Cache the column width so it can be used in + // updateTableColumnWidth() when determine which width to use + table.setData( + "column.width", + Integer.valueOf(tableColumn.getWidth()) + ); + } + + private void updateTableColumnWidth(Table table, + int width, + boolean verticalScrollbarBarVisible) { + + // Remove the border from the width + width -= (table.getBorderWidth() * 2); + + // Remove the scrollbar from the width if it is shown + if (verticalScrollbarBarVisible) { + width -= table.getVerticalBar().getSize().x; + } + + TableColumn tableColumn = table.getColumn(0); + + // Retrieve the cached column width, which is required for + // determining which width to use (the column width or the + // calculated width) + Integer columnWitdh = (Integer) table.getData("column.width"); + + // Use the calculated width if the column is smaller, otherwise + // use the column width and a horizontal scroll bar will show up + width = Math.max(width, columnWitdh); + + // Adjust the column width + tableColumn.setWidth(width); + } + }; + } + + private ITableLabelProvider buildTableLabelProvider(IBaseLabelProvider labelProvider) { + return new TableLabelProvider((ILabelProvider) labelProvider); + } + + /* + * (non-Javadoc) + */ + @Override + public Table getMainControl() { + return table; + } + + /* + * (non-Javadoc) + */ + @Override + @SuppressWarnings("unchecked") + protected void initializeMainComposite(Composite container, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + IBaseLabelProvider labelProvider, + String helpId) { + + table = addUnmanagedTable( + addTableContainer(container), + SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.MULTI, + helpId + ); + + + TableModelAdapter model = TableModelAdapter.adapt( + (ListValueModel<Object>) listHolder, + getSelectedItemHolder(), + table, + buildColumnAdapter(), + buildTableLabelProvider(labelProvider) + ); + + model.addSelectionChangeListener(buildSelectionListener()); + + selectedItemHolder.addPropertyChangeListener( + PropertyValueModel.VALUE, + buildSelectedItemPropertyChangeListener() + ); + + initializeTable(table); + } + + /** + * Initializes the given table, which acts like a list in our case. + * + * @param table The main widget of this pane + */ + protected void initializeTable(Table table) { + + table.setData("column.width", new Integer(0)); + table.setHeaderVisible(false); + table.setLinesVisible(false); + } + + /** + * The selection has changed, update (1) the selected item holder, (2) the + * selection model and (3) the buttons. + */ + private void selectionChanged() { + WritablePropertyValueModel<Object> selectedItemHolder = getSelectedItemHolder(); + ObjectListSelectionModel selectionModel = getSelectionModel(); + int selectionCount = this.table.getSelectionCount(); + + if (selectionCount == 0) { + selectedItemHolder.setValue(null); + selectionModel.clearSelection(); + } + else if (selectionCount != 1) { + selectedItemHolder.setValue(null); + selectionModel.clearSelection(); + + for (int index : this.table.getSelectionIndices()) { + selectionModel.addSelectionInterval(index, index); + } + } + else { + int selectedIndex = this.table.getSelectionIndex(); + Object selectedItem = getListHolder().get(selectedIndex); + + selectedItemHolder.setValue(selectedItem); + selectionModel.setSelectedValue(selectedItem); + } + + updateButtons(); + } + + /** + * This label provider simply delegates the rendering to the provided + * <code>ILabelProvider</code>. + */ + private class TableLabelProvider extends LabelProvider + implements ITableLabelProvider { + + private ILabelProvider labelProvider; + + TableLabelProvider(ILabelProvider labelProvider) { + super(); + this.labelProvider = labelProvider; + } + + public Image getColumnImage(Object element, int columnIndex) { + return labelProvider.getImage(element); + } + + public String getColumnText(Object element, int columnIndex) { + return labelProvider.getText(element); + } + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemovePane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemovePane.java new file mode 100644 index 0000000000..1ff01a483b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemovePane.java @@ -0,0 +1,923 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import java.util.Arrays; + +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jpt.common.ui.internal.JptCommonUiMessages; +import org.eclipse.jpt.common.ui.internal.listeners.SWTListChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.CollectionTools; +import org.eclipse.jpt.utility.internal.model.value.swing.ListModelAdapter; +import org.eclipse.jpt.utility.internal.model.value.swing.ObjectListSelectionModel; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.event.ListAddEvent; +import org.eclipse.jpt.utility.model.event.ListChangeEvent; +import org.eclipse.jpt.utility.model.event.ListClearEvent; +import org.eclipse.jpt.utility.model.event.ListMoveEvent; +import org.eclipse.jpt.utility.model.event.ListRemoveEvent; +import org.eclipse.jpt.utility.model.event.ListReplaceEvent; +import org.eclipse.jpt.utility.model.listener.ListChangeListener; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; + +/** + * The abstract definition of a pane that has buttons for adding, removing and + * possibly editing the items. + * + * @see AddRemoveListPane + * + * @version 1.0 + * @since 2.0 + */ +public abstract class AddRemovePane<T extends Model> extends Pane<T> +{ + private Adapter adapter; + private Button addButton; + private Composite container; + private boolean enabled; + private IBaseLabelProvider labelProvider; + private ListValueModel<?> listHolder; + private Button optionalButton; + private Button removeButton; + private WritablePropertyValueModel<Object> selectedItemHolder; + private ObjectListSelectionModel selectionModel; + + /** + * Creates a new <code>AddRemovePane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param adapter This <code>Adapter</code> is used to dictacte the behavior + * of this <code>AddRemovePane</code> and by delegating to it some of the + * behavior + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + */ + protected AddRemovePane(Pane<? extends T> parentPane, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + IBaseLabelProvider labelProvider) { + + this(parentPane, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider, + null); + } + + /** + * Creates a new <code>AddRemovePane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param adapter This <code>Adapter</code> is used to dictacte the behavior + * of this <code>AddRemovePane</code> and by delegating to it some of the + * behavior + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + * @param helpId The topic help ID to be registered with this pane + */ + protected AddRemovePane(Pane<? extends T> parentPane, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + IBaseLabelProvider labelProvider, + String helpId) { + + this(parentPane, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider, + helpId, + true); + } + /** + * Creates a new <code>AddRemovePane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param adapter This <code>Adapter</code> is used to dictacte the behavior + * of this <code>AddRemovePane</code> and by delegating to it some of the + * behavior + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + * @param helpId The topic help ID to be registered with this pane + * @param parentManagePane <code>true</code> to have the parent pane manage + * the enabled state of this pane + */ + protected AddRemovePane(Pane<? extends T> parentPane, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + IBaseLabelProvider labelProvider, + String helpId, + boolean parentManagePane) { + + super(parentPane, parent, true, parentManagePane); + + initialize( + adapter, + listHolder, + selectedItemHolder, + labelProvider + ); + + initializeLayout( + adapter, + listHolder, + selectedItemHolder, + labelProvider, + helpId + ); + } + + /** + * Creates a new <code>AddRemovePane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of the subject + * @param adapter This <code>Adapter</code> is used to dictacte the behavior + * of this <code>AddRemovePane</code> and by delegating to it some of the + * behavior + * @param parent The parent container + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + */ + protected AddRemovePane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + IBaseLabelProvider labelProvider) { + + this(parentPane, + subjectHolder, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider, + null); + } + + /** + * Creates a new <code>AddRemovePane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of the subject + * @param adapter This <code>Adapter</code> is used to dictacte the behavior + * of this <code>AddRemovePane</code> and by delegating to it some of the + * behavior + * @param parent The parent container + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + * @param helpId The topic help ID to be registered with this pane + */ + protected AddRemovePane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + IBaseLabelProvider labelProvider, + String helpId) { + + super(parentPane, subjectHolder, parent); + + initialize( + adapter, + listHolder, + selectedItemHolder, + labelProvider + ); + + initializeLayout( + adapter, + listHolder, + selectedItemHolder, + labelProvider, + helpId + ); + } + + /** + * Gives the possibility to add buttons after the Add button and before the + * optional button. + * + * @param container The parent container + * @param helpId The topic help ID to be registered with the buttons + * + * @category Layout + */ + protected void addCustomButtonAfterAddButton(Composite container, + String helpId) { + } + + /** + * Gives the possibility to add buttons after the optional button and before + * the Remove button. + * + * @param container The parent container + * @param helpId The topic help ID to be registered with the buttons + * + * @category Layout + */ + protected void addCustomButtonAfterOptionalButton(Composite container, + String helpId) { + } + + /** + * @category Add + */ + protected void addItem() { + adapter.addNewItem(selectionModel); + } + + /** + * @category Initialize + */ + protected Adapter buildAdapter() { + return adapter; + } + + /** + * @category Add + */ + protected Button addAddButton(Composite parent) { + return addUnmanagedButton( + parent, + adapter.addButtonText(), + buildAddItemAction() + ); + } + + /** + * @category Add + */ + private Runnable buildAddItemAction() { + return new Runnable() { + public void run() { + AddRemovePane.this.addItem(); + } + }; + } + + private ListChangeListener buildListChangeListener() { + return new SWTListChangeListenerWrapper(buildListChangeListener_()); + } + + private ListChangeListener buildListChangeListener_() { + return new ListChangeListener() { + + public void itemsAdded(ListAddEvent e) { + AddRemovePane.this.itemsAdded(e); + } + + public void itemsMoved(ListMoveEvent e) { + AddRemovePane.this.itemsMoved(e); + } + + public void itemsRemoved(ListRemoveEvent e) { + AddRemovePane.this.itemsRemoved(e); + } + + public void itemsReplaced(ListReplaceEvent e) { + AddRemovePane.this.itemsReplaced(e); + } + + public void listChanged(ListChangeEvent e) { + AddRemovePane.this.listChanged(e); + } + + public void listCleared(ListClearEvent e) { + AddRemovePane.this.listCleared(e); + } + }; + } + + protected void itemsAdded(ListAddEvent e) { + + } + + protected void itemsMoved(ListMoveEvent e) { + + } + + protected void itemsRemoved(ListRemoveEvent e) { + Object selectedItem = this.selectedItemHolder.getValue(); + + if (selectedItem == null) { + updateButtons(); + return; + } + + if (CollectionTools.contains(e.getItems(), selectedItem)) { + this.selectedItemHolder.setValue(null); + updateButtons(); + } + } + + protected void itemsReplaced(ListReplaceEvent e) { + + } + + protected void listChanged(ListChangeEvent e) { + + } + + protected void listCleared(ListClearEvent e) { + this.selectedItemHolder.setValue(null); + updateButtons(); + } + + + /** + * @category Option + */ + private Runnable buildOptionalAction() { + return new Runnable() { + public void run() { + AddRemovePane.this.editItem(); + } + }; + } + + /** + * @category Option + */ + protected Button addOptionalButton(Composite container) { + return addUnmanagedButton( + container, + adapter.optionalButtonText(), + buildOptionalAction() + ); + } + + /** + * @category Add + */ + protected Button addRemoveButton(Composite parent) { + return addUnmanagedButton( + parent, + adapter.removeButtonText(), + buildRemoveItemsAction() + ); + } + + /** + * @category Remove + */ + private Runnable buildRemoveItemsAction() { + return new Runnable() { + public void run() { + AddRemovePane.this.removeItems(); + } + }; + } + + protected ObjectListSelectionModel buildRowSelectionModel(ListValueModel<?> listModel) { + return new ObjectListSelectionModel(new ListModelAdapter(listModel)); + } + + /** + * @category Option + */ + protected void editItem() { + this.adapter.optionOnSelection(getSelectionModel()); + } + + /* + * (non-Javadoc) + */ + @Override + public void enableWidgets(boolean enabled) { + + super.enableWidgets(enabled); + this.enabled = enabled; + + if (!this.getMainControl().isDisposed()) { + this.getMainControl().setEnabled(enabled); + } + + this.updateButtons(); + } + + protected final Composite getContainer() { + return container; + } + + protected IBaseLabelProvider getLabelProvider() { + return labelProvider; + } + + protected final ListValueModel<?> getListHolder() { + return listHolder; + } + + /** + * Returns + * + * @return + */ + public abstract Composite getMainControl(); + + protected final WritablePropertyValueModel<Object> getSelectedItemHolder() { + return selectedItemHolder; + } + + public final ObjectListSelectionModel getSelectionModel() { + return selectionModel; + } + + /** + * Initializes this add/remove pane. + * + * @param adapter This <code>Adapter</code> is used to dictacte the behavior + * of this <code>AddRemovePane</code> and by delegating to it some of the + * behavior + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + * + * @category Initialization + */ + @SuppressWarnings("unchecked") + protected void initialize(Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + IBaseLabelProvider labelProvider) + { + this.listHolder = listHolder; + this.labelProvider = labelProvider; + this.adapter = (adapter == null) ? buildAdapter() : adapter; + this.selectedItemHolder = (WritablePropertyValueModel<Object>) selectedItemHolder; + this.selectionModel = new ObjectListSelectionModel(new ListModelAdapter(listHolder)); + + this.listHolder.addListChangeListener( + ListValueModel.LIST_VALUES, + buildListChangeListener() + ); + } + + /** + * Initializes the pane containing the buttons (Add, optional (if required) + * and Remove). + * + * @param container The parent container + * @param helpId The topic help ID to be registered with the buttons + * + * @category Layout + */ + protected void initializeButtonPane(Composite container, String helpId) { + + container = addSubPane(container); + + GridData gridData = new GridData(); + gridData.grabExcessVerticalSpace = true; + gridData.verticalAlignment = SWT.TOP; + container.setLayoutData(gridData); + + // Add button + this.addButton = addAddButton(container); + addAlignRight(this.addButton); + + // Custom button + addCustomButtonAfterAddButton(container, helpId); + + // Optional button + if (this.adapter.hasOptionalButton()) { + this.optionalButton = addOptionalButton(container); + addAlignRight(this.optionalButton); + } + + // Custom button + addCustomButtonAfterOptionalButton(container, helpId); + + // Remove button + removeButton = addRemoveButton(container); + addAlignRight(removeButton); + + // Update the help topic ID + if (helpId != null) { + getHelpSystem().setHelp(addButton, helpId); + getHelpSystem().setHelp(removeButton, helpId); + + if (optionalButton != null) { + getHelpSystem().setHelp(optionalButton, helpId); + } + } + } + + /** + * Initializes this add/remove pane by creating the widgets. The subclass is + * required to build the main widget. + * + * @param adapter This <code>Adapter</code> is used to dictacte the behavior + * of this <code>AddRemovePane</code> and by delegating to it some of the + * behavior + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + * @param helpId The topic help ID to be registered with this pane + * + * @category Layout + */ + protected void initializeLayout(Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + IBaseLabelProvider labelProvider, + String helpId) { + + initializeMainComposite( + container, + adapter, + listHolder, + selectedItemHolder, + labelProvider, + helpId); + + initializeButtonPane(container, helpId); + enableWidgets(getSubject() != null); + } + + /** + * {@inheritDoc} + */ + @Override + protected void initializeLayout(Composite container) { + this.container = addSubPane(container, 2, 0, 0, 0, 0); + } + + /** + * Initializes the main widget of this add/remove pane. + * + * @param container The parent container + * @param adapter This <code>Adapter</code> is used to dictacte the behavior + * of this <code>AddRemovePane</code> and by delegating to it some of the + * behavior + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + * @param helpId The topic help ID to be registered with this pane or + * <code>null</code> if it was not specified + * + * @category Layout + */ + protected abstract void initializeMainComposite(Composite container, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + IBaseLabelProvider labelProvider, + String helpId); + + /** + * @category Remove + */ + protected void removeItems() { + + // Keep track of the selected indices so we can select an item + // before the lowest index + int[] indices = selectionModel.selectedIndices(); + Arrays.sort(indices); + + // Notify the adapter to remove the selected items + adapter.removeSelectedItems(selectionModel); + + // Select a new item + if (getListHolder().size() > 0) { + int index = Math.min(indices[0], getListHolder().size() - 1); + Object item = getListHolder().get(index); + selectedItemHolder.setValue(item); + } + // The list is empty, clear the value + else { + selectedItemHolder.setValue(null); + } + } + + /** + * Selects the given value, which can be <code>null</code>. + * + * @param value The new selected value + */ + public void setSelectedItem(Object value) { + selectedItemHolder.setValue(value); + } + + /** + * @category UpdateButtons + */ + protected void updateAddButton(Button addButton) { + addButton.setEnabled(enabled); + } + + /** + * @category UpdateButtons + */ + protected void updateButtons() { + if (!container.isDisposed()) { + updateAddButton(addButton); + updateRemoveButton(removeButton); + updateOptionalButton(optionalButton); + } + } + + /** + * @category UpdateButtons + */ + protected void updateOptionalButton(Button optionalButton) { + if (optionalButton != null) { + optionalButton.setEnabled( + enabled && + adapter.enableOptionOnSelectionChange(selectionModel) + ); + } + } + + /** + * @category UpdateButtons + */ + protected void updateRemoveButton(Button removeButton) { + removeButton.setEnabled( + enabled && + adapter.enableRemoveOnSelectionChange(selectionModel) + ); + } + + /** + * An abstract implementation of <code>Adapter</code>. + */ + public static abstract class AbstractAdapter implements Adapter { + + /** + * The text of the add button. + */ + private String addButtonText; + + /** + * Determines whether the optional button should be shown or not. + */ + private boolean hasOptionalButton; + + /** + * The text of the optional button, if used. + */ + private String optionalButtonText; + + /** + * The text of the remove button. + */ + private String removeButtonText; + + /** + * Creates a new <code>AbstractAdapter</code> with default text for the + * add and remove buttons. + */ + public AbstractAdapter() { + this(JptCommonUiMessages.AddRemovePane_AddButtonText, + JptCommonUiMessages.AddRemovePane_RemoveButtonText); + } + + /** + * Creates a new <code>AbstractAdapter</code> with default text for the + * add and remove buttons. + * + * @param hasOptionalButton <code>true</code> to show an optional button + * and to use the behavior related to the optional button; + * <code>false</code> to not use it + */ + public AbstractAdapter(boolean hasOptionalButton) { + this(); + this.setHasOptionalButton(hasOptionalButton); + } + + /** + * Creates a new <code>AbstractAdapter</code> with default text for the + * add and remove buttons. + * + * @param optionalButtonText The text of the optional button, which means + * the optional button will be shown + */ + public AbstractAdapter(String optionalButtonText) { + this(true); + this.setOptionalButtonText(optionalButtonText); + } + + /** + * Creates a new <code>AbstractAdapter</code>. + * + * @param addButtonText The add button's text + * @param removeButtonText The remove button's text + */ + public AbstractAdapter(String addButtonText, + String removeButtonText) { + + super(); + this.addButtonText = addButtonText; + this.removeButtonText = removeButtonText; + } + + /** + * Creates a new <code>AbstractAdapter</code>. + * + * @param addButtonText The add button's text + * @param removeButtonText The remove button's text + * @param optionalButtonText The text of the optional button, which means + * the optional button will be shown + */ + public AbstractAdapter(String addButtonText, + String removeButtonText, + String optionalButtonText) { + + this(optionalButtonText); + this.setAddButtonText(addButtonText); + this.setRemoveButtonText(removeButtonText); + } + + /* + * (non-Javadoc) + */ + public String addButtonText() { + return addButtonText; + } + + /* + * (non-Javadoc) + */ + public boolean enableOptionOnSelectionChange(ObjectListSelectionModel listSelectionModel) { + return listSelectionModel.selectedValuesSize() == 1; + } + + public boolean enableRemoveOnSelectionChange(ObjectListSelectionModel listSelectionModel) { + return listSelectionModel.selectedValue() != null; + } + + /* + * (non-Javadoc) + */ + public boolean hasOptionalButton() { + return hasOptionalButton; + } + + /* + * (non-Javadoc) + */ + public String optionalButtonText() { + return optionalButtonText; + } + + /* + * (non-Javadoc) + */ + public void optionOnSelection(ObjectListSelectionModel listSelectionModel) { + } + + /* + * (non-Javadoc) + */ + public String removeButtonText() { + return removeButtonText; + } + + /** + * Changes the text of the add button. This method has to be called before + * the <code>AddRemoveListPane</code> is initialized. + * + * @param addButtonText The add button's text + */ + public void setAddButtonText(String addButtonText) { + this.addButtonText = addButtonText; + } + + /** + * Changes the state of the optional button, meaning if it should be shown + * between the add and remove buttons or not. + * + * @param hasOptionalButton <code>true</code> to show an optional button + * and to use the behavior related to the optional button; + * <code>false</code> to not use it + */ + public void setHasOptionalButton(boolean hasOptionalButton) { + this.hasOptionalButton = hasOptionalButton; + } + + /** + * Changes the text of the optional button. This method has to be called + * before the <code>AddRemoveListPane</code> is initialized. This does not + * make the optional button visible. + * + * @param optionalButtonText The optional button's text + */ + public void setOptionalButtonText(String optionalButtonText) { + this.optionalButtonText = optionalButtonText; + } + + /** + * Changes the text of the remove button. This method has to be called + * before the <code>AddRemoveListPane</code> is initialized. + * + * @param removeButtonText The remove button's text + */ + public void setRemoveButtonText(String removeButtonText) { + this.removeButtonText = removeButtonText; + } + } + + /** + * This adapter is used to perform the actual action when adding a new item + * or removing the selected items. It is possible to add an optional button. + */ + public static interface Adapter { + + /** + * The add button's text. + * + * @return The text shown on the add button + */ + String addButtonText(); + + /** + * Invoked when the user selects the Add button. + */ + void addNewItem(ObjectListSelectionModel listSelectionModel); + + /** + * Invoked when selection changes. Implementation dictates whether button + * should be enabled. + */ + boolean enableOptionOnSelectionChange(ObjectListSelectionModel listSelectionModel); + + /** + * Invoked when selection changes. Implementation dictates whether remove button + * should be enabled. + */ + boolean enableRemoveOnSelectionChange(ObjectListSelectionModel listSelectionModel); + + /** + * Determines whether an optional button should be added between the add + * and remove buttons. + * + * @return <code>true</code> to show an optional button and to use the + * behavior related to the optional button; <code>false</code> to not use + * it + */ + boolean hasOptionalButton(); + + /** + * Resource string key for the optional button. + */ + String optionalButtonText(); + + /** + * Invoked when the user selects the optional button + */ + void optionOnSelection(ObjectListSelectionModel listSelectionModel); + + /** + * The remove button's text. + * + * @return The text shown on the remove button + */ + String removeButtonText(); + + /** + * Invoked when the user selects the Remove button. + */ + void removeSelectedItems(ObjectListSelectionModel listSelectionModel); + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemoveTablePane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemoveTablePane.java new file mode 100644 index 0000000000..5befb986ad --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemoveTablePane.java @@ -0,0 +1,314 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.ui.internal.swt.ColumnAdapter; +import org.eclipse.jpt.common.ui.internal.swt.TableModelAdapter; +import org.eclipse.jpt.common.ui.internal.swt.TableModelAdapter.SelectionChangeEvent; +import org.eclipse.jpt.common.ui.internal.swt.TableModelAdapter.SelectionChangeListener; +import org.eclipse.jpt.utility.internal.CollectionTools; +import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel; +import org.eclipse.jpt.utility.internal.model.value.swing.ObjectListSelectionModel; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; + +/** + * This implementation of the <code>AddRemovePane</code> uses a <code>Table</code> + * as its main widget. + * <p> + * Here the layout of this pane: + * <pre> + * ----------------------------------------------------------------------------- + * | ------------------------------------------------------------- ----------- | + * | | Column 1 | Column 2 | ... | Column i | ... | Colunm n | | Add... | | + * | |-----------------------------------------------------------| ----------- | + * | | | | | | | | ----------- | + * | |-----------------------------------------------------------| | Edit... | | + * | | | | | | | | ----------- | + * | |-----------------------------------------------------------| ----------- | + * | | | | | | | | | Remove | | + * | |-----------------------------------------------------------| ----------- | + * | ------------------------------------------------------------- | + * -----------------------------------------------------------------------------</pre> + * + * @version 2.0 + * @since 1.0 + */ +public abstract class AddRemoveTablePane<T extends Model> extends AddRemovePane<T> +{ + /** + * Flag used to prevent circular + */ + private boolean locked; + + /** + * The main widget of this add/remove pane. + */ + private Table table; + + /** + * Creates a new <code>AddRemoveTablePane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param adapter + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + */ + public AddRemoveTablePane(Pane<? extends T> parentPane, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + ITableLabelProvider labelProvider) { + + super(parentPane, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider); + + } + + /** + * Creates a new <code>AddRemoveTablePane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param adapter + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + * @param helpId The topic help ID to be registered with this pane + */ + public AddRemoveTablePane(Pane<? extends T> parentPane, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + ITableLabelProvider labelProvider, + String helpId) { + + super(parentPane, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider, + helpId); + } + + /** + * Creates a new <code>AddRemoveTablePane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of the subject + * @param adapter + * @param parent The parent container + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + */ + public AddRemoveTablePane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + ITableLabelProvider labelProvider) { + + super(parentPane, + subjectHolder, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider); + } + + /** + * Creates a new <code>AddRemoveTablePane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of the subject + * @param adapter + * @param parent The parent container + * @param listHolder The <code>ListValueModel</code> containing the items + * @param selectedItemHolder The holder of the selected item, if more than + * one item or no items are selected, then <code>null</code> will be passed + * @param labelProvider The renderer used to format the list holder's items + * @param helpId The topic help ID to be registered with this pane + */ + public AddRemoveTablePane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + ITableLabelProvider labelProvider, + String helpId) { + + super(parentPane, + subjectHolder, + parent, + adapter, + listHolder, + selectedItemHolder, + labelProvider, + helpId); + } + + protected abstract ColumnAdapter<?> buildColumnAdapter(); + + private WritablePropertyValueModel<Object> buildSelectedItemHolder() { + return new SimplePropertyValueModel<Object>(); + } + + private PropertyChangeListener buildSelectedItemPropertyChangeListener() { + return new SWTPropertyChangeListenerWrapper( + buildSelectedItemPropertyChangeListener_() + ); + } + + private PropertyChangeListener buildSelectedItemPropertyChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent e) { + if (table.isDisposed()) { + return; + } + + if (!locked) { + locked = true; + + try { + Object value = e.getNewValue(); + getSelectionModel().setSelectedValue(e.getNewValue()); + int index = -1; + + if (value != null) { + index = CollectionTools.indexOf(getListHolder().iterator(), value); + } + + table.select(index); + updateButtons(); + } + finally { + locked = false; + } + } + } + }; + } + + private SelectionChangeListener<Object> buildSelectionListener() { + return new SelectionChangeListener<Object>() { + public void selectionChanged(SelectionChangeEvent<Object> e) { + AddRemoveTablePane.this.selectionChanged(); + } + }; + } + + /* + * (non-Javadoc) + */ + @Override + public Table getMainControl() { + return table; + } + + /* + * (non-Javadoc) + */ + @Override + @SuppressWarnings("unchecked") + protected void initializeMainComposite(Composite container, + Adapter adapter, + ListValueModel<?> listHolder, + WritablePropertyValueModel<?> selectedItemHolder, + IBaseLabelProvider labelProvider, + String helpId) + { + table = addUnmanagedTable(container, helpId); + table.setHeaderVisible(true); + + TableModelAdapter<Object> tableModel = TableModelAdapter.adapt( + (ListValueModel<Object>) listHolder, + buildSelectedItemHolder(), + table, + (ColumnAdapter<Object>) buildColumnAdapter(), + (ITableLabelProvider) labelProvider + ); + + tableModel.addSelectionChangeListener(buildSelectionListener()); + + selectedItemHolder.addPropertyChangeListener( + PropertyValueModel.VALUE, + buildSelectedItemPropertyChangeListener() + ); + } + + /** + * The selection has changed, update (1) the selected item holder, (2) the + * selection model and (3) the buttons. + */ + private void selectionChanged() { + + if (locked) { + return; + } + + locked = true; + + try { + WritablePropertyValueModel<Object> selectedItemHolder = getSelectedItemHolder(); + ObjectListSelectionModel selectionModel = getSelectionModel(); + int selectionCount = table.getSelectionCount(); + + if (selectionCount == 0) { + selectedItemHolder.setValue(null); + selectionModel.clearSelection(); + } + else if (selectionCount != 1) { + selectedItemHolder.setValue(null); + selectionModel.clearSelection(); + + for (int index : table.getSelectionIndices()) { + selectionModel.addSelectionInterval(index, index); + } + } + else { + int selectedIndex = table.getSelectionIndex(); + Object selectedItem = getListHolder().get(selectedIndex); + + selectedItemHolder.setValue(selectedItem); + selectionModel.setSelectedValue(selectedItem); + } + + updateButtons(); + } + finally { + locked = false; + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ChooserPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ChooserPane.java new file mode 100644 index 0000000000..e4177f129a --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ChooserPane.java @@ -0,0 +1,167 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jpt.common.ui.internal.JptCommonUiMessages; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * A chooser is simply a pane with three widgets, the label on the left, a main + * widget, usually a text field, and a right widget which is usually a browse + * button. + * + * @see ClassChooserPane + * @see PackageChooserPane + * + * @version 3.0 + * @since 2.0 + */ +public abstract class ChooserPane<T extends Model> extends Pane<T> +{ + /** + * The control shown after the label (left control). + */ + private Control mainControl; + + /** + * The control shown after the main control. + */ + private Control rightControl; + + /** + * Creates a new <code>ChooserPane</code>. + * + * @param parentPane The parent pane of this one + * @param parent The parent container + */ + public ChooserPane(Pane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + /** + * Creates a new <code>ChooserPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + */ + public ChooserPane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + @Override + protected void initializeLayout(Composite container) { + + this.mainControl = addMainControl(container); + this.rightControl = addRightControl(container); + + addLabeledComposite( + container, + addLeftControl(container), + this.mainControl, + this.rightControl, + getHelpId() + ); + } + + /** + * Creates the left control. By default a label is created and its text is + * retrieved by {@link #getLabelText()}. + * + * @param container The parent container + * @return The newly created left control + */ + protected Control addLeftControl(Composite container) { + return addLabel(container, getLabelText()); + } + + /** + * The text of the label. This method is called by + * {@link #buildLeftControl(Composite)}. + * + * @return The localized text of the left control (which is a label by + * default) + */ + protected abstract String getLabelText(); + + /** + * Creates the main control of this pane. + * + * @param container The parent container + * @return The newly created main control + */ + protected abstract Control addMainControl(Composite container); + + /** + * Creates the right control. By default a browse button is created and its + * action is performed by {@link #buildBrowseAction()} and its text is + * retrieved by {@link #getBrowseButtonText()}. + * + * @param container The parent container + * @return The newly created right control + */ + protected Control addRightControl(Composite container) { + return addButton( + container, + getBrowseButtonText(), + buildBrowseAction() + ); + } + + /** + * Returns the text of the browse button. This method is called by + * {@link #buildRightControl(Composite)}. + * + * @return "Browse..." + */ + protected String getBrowseButtonText() { + return JptCommonUiMessages.ChooserPane_browseButton; + } + + /** + * Creates the action responsible to perform the action when the Browse is + * clicked. + * + * @return A new <code>Runnable</code> performing the actual action of the + * button + */ + protected abstract Runnable buildBrowseAction(); + + /** + * Returns the help topic ID for the controls of this pane. + * + * @return <code>null</code> is returned otherwise the subclass can return an ID + */ + protected String getHelpId() { + return null; + } + + @Override + public void enableWidgets(boolean enabled) { + + super.enableWidgets(enabled); + + if (!this.mainControl.isDisposed()) { + this.mainControl.setEnabled(enabled); + } + + if (!this.rightControl.isDisposed()) { + this.rightControl.setEnabled(enabled); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ClassChooserComboPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ClassChooserComboPane.java new file mode 100644 index 0000000000..3ec910c76f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ClassChooserComboPane.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jdt.internal.ui.refactoring.contentassist.ControlContentAssistHelper; +import org.eclipse.jpt.utility.internal.StringConverter; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * This chooser allows the user to choose a type when browsing and it adds code + * completion support to the text field, which is the main component. + * <p> + * Here the layout of this pane: + * <pre> + * ----------------------------------------------------------------------------- + * | ---------------------------------------------------- ------------- | + * | Label: | I X | | Browse... | | + * | ---------------------------------------------------- ------------- | + * -----------------------------------------------------------------------------</pre> + * + * @version 2.0 + * @since 2.0 + */ +public abstract class ClassChooserComboPane<T extends Model> extends ClassChooserPane<T> +{ + + /** + * Creates a new <code>ClassChooserPane</code>. + * + * @param parentPane The parent pane of this one + * @param parent The parent container + */ + public ClassChooserComboPane(Pane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + /** + * Creates a new <code>ClassChooserPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + */ + public ClassChooserComboPane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + @Override + protected Control addMainControl(Composite container) { + Composite subPane = addSubPane(container); + Combo combo = this.addClassCombo(subPane); + + ControlContentAssistHelper.createComboContentAssistant( + combo, + javaTypeCompletionProcessor + ); + + return subPane; + } + + protected Combo addClassCombo(Composite container) { + return this.addEditableCombo( + container, + this.buildClassListHolder(), + this.buildTextHolder(), + this.buildClassConverter() + ); + } + + protected abstract ListValueModel<String> buildClassListHolder(); + + protected StringConverter<String> buildClassConverter() { + return StringConverter.Default.instance(); + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ClassChooserPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ClassChooserPane.java new file mode 100644 index 0000000000..f68ad8c788 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ClassChooserPane.java @@ -0,0 +1,368 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.internal.ui.refactoring.contentassist.ControlContentAssistHelper; +import org.eclipse.jdt.internal.ui.refactoring.contentassist.JavaTypeCompletionProcessor; +import org.eclipse.jdt.internal.ui.wizards.NewClassCreationWizard; +import org.eclipse.jdt.ui.IJavaElementSearchConstants; +import org.eclipse.jdt.ui.JavaUI; +import org.eclipse.jdt.ui.wizards.NewClassWizardPage; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.window.Window; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.jpt.common.core.internal.utility.jdt.JDTTools; +import org.eclipse.jpt.common.ui.JptCommonUiPlugin; +import org.eclipse.jpt.common.ui.internal.JptCommonUiMessages; +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.internal.ClassName; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.SelectionDialog; +import org.eclipse.ui.forms.widgets.Hyperlink; + +/** + * This chooser allows the user to choose a type when browsing and it adds code + * completion support to the text field, which is the main component. + * <p> + * Here the layout of this pane: + * <pre> + * ----------------------------------------------------------------------------- + * | ---------------------------------------------------- ------------- | + * | Label: | I | | Browse... | | + * | ---------------------------------------------------- ------------- | + * -----------------------------------------------------------------------------</pre> + * + * @version 2.3 + * @since 2.0 + */ +@SuppressWarnings("nls") +public abstract class ClassChooserPane<T extends Model> extends ChooserPane<T> +{ + /** + * The code completion manager. + */ + protected JavaTypeCompletionProcessor javaTypeCompletionProcessor; + + private PropertyChangeListener subjectChangeListener; + + /** + * Creates a new <code>ClassChooserPane</code>. + * + * @param parentPane The parent pane of this one + * @param parent The parent container + */ + public ClassChooserPane(Pane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + /** + * Creates a new <code>ClassChooserPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + */ + public ClassChooserPane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + @Override + protected void initialize() { + super.initialize(); + + // TODO bug 156185 - when this is fixed there should be api for this + this.javaTypeCompletionProcessor = new JavaTypeCompletionProcessor(false, false); + + this.subjectChangeListener = this.buildSubjectChangeListener(); + this.getSubjectHolder().addPropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener); + + this.classChooserSubjectChanged(getSubject()); + } + + private PropertyChangeListener buildSubjectChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildSubjectChangeListener_()); + } + + private PropertyChangeListener buildSubjectChangeListener_() { + return new PropertyChangeListener() { + @SuppressWarnings("unchecked") + public void propertyChanged(PropertyChangeEvent e) { + ClassChooserPane.this.classChooserSubjectChanged((T) e.getNewValue()); + } + }; + } + + protected void classChooserSubjectChanged(T newSubject) { + IPackageFragment packageFragment = null; + if (newSubject != null) { + IPackageFragmentRoot root = getPackageFragmentRoot(); + if (root != null) { + packageFragment = root.getPackageFragment(""); + } + } + this.javaTypeCompletionProcessor.setPackageFragment(packageFragment); + } + + @Override + protected Control addLeftControl(Composite container) { + if( ! this.allowTypeCreation()) { + return super.addLeftControl(container); + } + Hyperlink labelLink = this.addHyperlink(container, + this.getLabelText(), + this.buildHyperLinkAction() + ); + return labelLink; + } + + private Runnable buildHyperLinkAction() { + return new Runnable() { + public void run() { + ClassChooserPane.this.hyperLinkSelected(); + } + }; + } + + protected void hyperLinkSelected() { + IType type = getType(); + if (type != null) { + openInEditor(type); + } + else if (allowTypeCreation()){ + createType(); + } + } + + protected IType getType() { + if (getClassName() == null) { + return null; + } + IType type = null; + try { + type = getJavaProject().findType(getClassName().replace('$', '.')); + } + catch (JavaModelException e) { + JptCommonUiPlugin.log(e); + } + return type; + } + + protected void createType() { + StructuredSelection selection = new StructuredSelection(getJavaProject().getProject()); + + NewClassWizardPage newClassWizardPage = new NewClassWizardPage(); + newClassWizardPage.init(selection); + newClassWizardPage.setSuperClass(getSuperclassName(), true); + newClassWizardPage.setSuperInterfaces(getSuperInterfaceNames(), true); + if (!StringTools.stringIsEmpty(getClassName())) { + newClassWizardPage.setTypeName(ClassName.getSimpleName(getClassName()), true); + String packageName = ClassName.getPackageName(getClassName()); + newClassWizardPage.setPackageFragment(getFirstJavaSourceFolder().getPackageFragment(packageName), true); + } + NewClassCreationWizard wizard = new NewClassCreationWizard(newClassWizardPage, false); + wizard.init(PlatformUI.getWorkbench(), selection); + + WizardDialog dialog = new WizardDialog(getShell(), wizard); + dialog.create(); + int dResult = dialog.open(); + if (dResult == Window.OK) { + String className = (newClassWizardPage.getCreatedType()).getFullyQualifiedName(getEnclosingTypeSeparator()); + setClassName(className); + } + } + + protected abstract void setClassName(String className); + + /** + * Override this to change the enclosing type separator + */ + protected char getEnclosingTypeSeparator() { + return '$'; + } + + /** + * Override this to set a superclass in the New Class wizard. If no class is chosen, + * clicking the hyperlink label will open the new class wizard. + */ + protected String getSuperclassName() { + return ""; + } + + /** + * Override this to set a super interface in the New Class wizard. If no class is chosen, + * clicking the hyperlink label will open the new class wizard. + * @see getSuperInterfaceName + */ + protected List<String> getSuperInterfaceNames() { + return getSuperInterfaceName() != null ? Collections.singletonList(getSuperInterfaceName()) : Collections.<String>emptyList(); + } + + /** + * Override this to set a super interface in the New Class wizard. If no class is chosen, + * clicking the hyperlink label will open the new class wizard. + */ + protected String getSuperInterfaceName() { + return null; + } + + /** + * Override this if it does not make sense to allow the user to create a new type. + * This will determine whether clicking the hyperlink opens the New Class wizard + * @return + */ + protected boolean allowTypeCreation() { + return true; + } + + protected void openInEditor(IType type) { + IJavaElement javaElement = type.getParent(); + try { + JavaUI.openInEditor(javaElement, true, true); + } + catch (JavaModelException e) { + JptCommonUiPlugin.log(e); + } + catch (PartInitException e) { + JptCommonUiPlugin.log(e); + } + } + + protected abstract IJavaProject getJavaProject(); + + @Override + protected final Runnable buildBrowseAction() { + return new Runnable() { + public void run() { + promptType(); + } + }; + } + + @Override + protected Control addMainControl(Composite container) { + Composite subPane = addSubPane(container); + Text text = addText(subPane, buildTextHolder()); + + ControlContentAssistHelper.createTextContentAssistant( + text, + javaTypeCompletionProcessor + ); + + return subPane; + } + + /** + * Creates the value holder of the subject's property. + * + * @return The holder of the class name + */ + protected abstract WritablePropertyValueModel<String> buildTextHolder(); + + /** + * Prompts the user the Open Type dialog. + * + * @return Either the selected type or <code>null</code> if the user + * cancelled the dialog + */ + protected IType chooseType() { + IJavaElement[] elements = new IJavaElement[] { getJavaProject() }; + IJavaSearchScope scope = SearchEngine.createJavaSearchScope(elements); + SelectionDialog typeSelectionDialog; + + try { + typeSelectionDialog = JavaUI.createTypeDialog( + getShell(), + PlatformUI.getWorkbench().getProgressService(), + scope, + getTypeDialogStyle(), + false, + StringTools.stringIsEmpty(getClassName()) ? "" : ClassName.getSimpleName(getClassName()) + ); + } + catch (JavaModelException e) { + JptCommonUiPlugin.log(e); + return null; + } + + typeSelectionDialog.setTitle(JptCommonUiMessages.ClassChooserPane_dialogTitle); + typeSelectionDialog.setMessage(JptCommonUiMessages.ClassChooserPane_dialogMessage); + + if (typeSelectionDialog.open() == Window.OK) { + return (IType) typeSelectionDialog.getResult()[0]; + } + + return null; + } + + protected int getTypeDialogStyle() { + return IJavaElementSearchConstants.CONSIDER_CLASSES; + } + + /** + * Returns the class name from its subject. + * + * @return The class name or <code>null</code> if none is defined + */ + protected abstract String getClassName(); + + protected IPackageFragmentRoot getFirstJavaSourceFolder() { + Iterator<IPackageFragmentRoot> i = JDTTools.getJavaSourceFolders(getJavaProject()).iterator(); + return i.hasNext() ? i.next() : null; + } + + /** + * The browse button was clicked, its action invokes this action which should + * prompt the user to select a class and set it. + */ + protected void promptType() { + IType type = this.chooseType(); + + if (type != null) { + String className = type.getFullyQualifiedName(getEnclosingTypeSeparator()); + setClassName(className); + } + } + + protected IPackageFragmentRoot getPackageFragmentRoot() { + return JDTTools.getCodeCompletionContextRoot(getJavaProject()); + } + + @Override + public void dispose() { + this.getSubjectHolder().removePropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener); + super.dispose(); + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ComboPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ComboPane.java new file mode 100644 index 0000000000..940dabbd57 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ComboPane.java @@ -0,0 +1,289 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jpt.common.ui.internal.JptCommonUiMessages; +import org.eclipse.jpt.common.ui.internal.util.SWTUtil; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.internal.Tools; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; + +/** + * Pane with combo box support for automatic updating of: + * - selected value + * - default value + * - value choices + */ +public abstract class ComboPane<T extends Model> + extends Pane<T> +{ + /** + * The main (only) widget of this pane. + */ + protected Combo comboBox; + + + // **************** constructors ****************************************** + + protected ComboPane( + Pane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + protected ComboPane( + Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + protected ComboPane( + Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + PropertyValueModel<Boolean> enabledModel) { + + super(parentPane, subjectHolder, parent, enabledModel); + } + + + // **************** initialization **************************************** + + @Override + protected void initializeLayout(Composite container) { + this.comboBox = this.addEditableCombo(container); + this.comboBox.addModifyListener(this.buildModifyListener()); + SWTUtil.attachDefaultValueHandler(this.comboBox); + } + + protected ModifyListener buildModifyListener() { + return new ModifyListener() { + public void modifyText(ModifyEvent e) { + ComboPane.this.comboBoxModified(); + } + }; + } + + + // **************** typical overrides ************************************* + + /** + * Return the possible values to be added to the combo during + * population. + */ + protected abstract Iterable<String> getValues(); + + /** + * Return whether the combo is to add a default value to the choices + */ + protected boolean usesDefaultValue() { + // default response is 'true' + return true; + } + + /** + * Return the default value, or <code>null</code> if no default is + * specified. This method is only called when the subject is non-null. + */ + protected abstract String getDefaultValue(); + + /** + * Return the current value from the subject. + * This method is only called when the subject is non-null. + */ + protected abstract String getValue(); + + /** + * Set the specified value as the new value on the subject. + */ + protected abstract void setValue(String value); + + + // **************** overrides ********************************************* + + @Override + protected void propertyChanged(String propertyName) { + super.propertyChanged(propertyName); + this.updateSelectedItem(); + } + + @Override + protected void doPopulate() { + super.doPopulate(); + this.populateComboBox(); + } + + + // **************** populating ******************************************** + + /** + * Populate the combo-box list by clearing it, then adding first the default + * value, if available, and then the possible choices. + */ + protected void populateComboBox() { + this.comboBox.removeAll(); + + if (usesDefaultValue()) { + this.comboBox.add(this.buildDefaultValueEntry()); + } + + for (String value : this.getValues()) { + this.comboBox.add(value); + } + + this.updateSelectedItem_(); + } + + protected String buildDefaultValueEntry() { + if (getSubject() == null) { + return JptCommonUiMessages.NoneSelected; + } + String defaultValue = this.getDefaultValue(); + return (defaultValue == null) ? this.buildNullDefaultValueEntry() : this.buildNonNullDefaultValueEntry(defaultValue); + } + + protected String buildNullDefaultValueEntry() { + return JptCommonUiMessages.DefaultEmpty; + } + + protected String buildNonNullDefaultValueEntry(String defaultValue) { + return NLS.bind( + JptCommonUiMessages.DefaultWithOneParam, + defaultValue); + } + + protected void updateSelectedItem() { + // make sure the default value is up to date (??? ~bjv) + if (usesDefaultValue()) { + String defaultValueEntry = this.buildDefaultValueEntry(); + if ( ! this.comboBox.getItem(0).equals(defaultValueEntry)) { + this.comboBox.remove(0); + this.comboBox.add(defaultValueEntry, 0); + } + } + + this.updateSelectedItem_(); + } + + /** + * Updates the selected item by selecting the current value, if not + * <code>null</code>, or select the default value if one is available, + * otherwise remove the selection. + */ + protected void updateSelectedItem_() { + String value = (this.getSubject() == null) ? null : this.getValue(); + if (value == null) { + if (usesDefaultValue()) { + // select the default value + this.comboBox.select(0); + } + else { + this.comboBox.setText(""); + } + } else { + // select the new value + if ( ! value.equals(this.comboBox.getText())) { + // This prevents the cursor from being set back to the beginning of the line (bug 234418). + // The reason we are hitting this method at all is because the + // context model is updating from the resource model in a way + // that causes change notifications to be fired (the annotation + // is added to the resource model, change notification occurs + // on the update thread, and then the name is set, these 2 + // threads can get in the wrong order). + // The #valueChanged() method sets the populating flag to true, + // but in this case it is already set back to false when we + // receive notification back from the model because it has + // moved to the update thread and then jumps back on the UI thread. + this.comboBox.setText(value); + } + } + } + + protected void repopulateComboBox() { + if ( ! this.comboBox.isDisposed()) { + this.repopulate(); + } + } + + + // **************** combo-box listener callback *************************** + + protected void comboBoxModified() { + if ( ! this.isPopulating()) { + this.valueChanged(this.comboBox.getText()); + } + } + + /** + * The combo-box selection has changed, update the model if necessary. + * If the value has changed and the subject is null, we can build a subject + * before setting the value. + */ + protected void valueChanged(String newValue) { + T subject = this.getSubject(); + String oldValue; + if (subject == null) { + if (this.nullSubjectIsNotAllowed()) { + return; // no subject to set the value on + } + oldValue = null; + } else { + oldValue = this.getValue(); + } + + // convert empty string or default to null + if (StringTools.stringIsEmpty(newValue) || this.valueIsDefault(newValue)) { + newValue = null; + } + + // set the new value if it is different from the old value + if (Tools.valuesAreDifferent(oldValue, newValue)) { + this.setPopulating(true); + + try { + this.setValue(newValue); + } finally { + this.setPopulating(false); + } + } + } + + /** + * Return whether we can set the value when the subject is null + * (i.e. #setValue(String) will construct the subject if necessary). + */ + protected boolean nullSubjectIsAllowed() { + return false; + } + + protected final boolean nullSubjectIsNotAllowed() { + return ! this.nullSubjectIsAllowed(); + } + + /** + * pre-condition: value is not null + */ + protected boolean valueIsDefault(String value) { + if (! usesDefaultValue()) { + return false; + } + return (this.comboBox.getItemCount() > 0) + && value.equals(this.comboBox.getItem(0)); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/DefaultWidgetFactory.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/DefaultWidgetFactory.java new file mode 100644 index 0000000000..97f4dc388c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/DefaultWidgetFactory.java @@ -0,0 +1,260 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jpt.common.ui.WidgetFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CCombo; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.DateTime; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.List; +import org.eclipse.swt.widgets.Spinner; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Hyperlink; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.eclipse.ui.forms.widgets.TableWrapLayout; + +/** + * This <code>WidgetFactory</code> simply creates plain SWT widgets. + * + * @version 2.0 + * @since 2.0 + */ +public class DefaultWidgetFactory implements WidgetFactory { + + /** + * The singleton instance of this <code>IWidgetFactory</code> + */ + private static final WidgetFactory INSTANCE = new DefaultWidgetFactory(); + + /** + * Creates a new <code>DefaultWidgetFactory</code>. + */ + private DefaultWidgetFactory() { + super(); + } + + /** + * Returns the singleton instance of this <code>IWidgetFactory</code>. + * + * @return The singleton instance of this <code>IWidgetFactory</code> + */ + public static WidgetFactory instance() { + return INSTANCE; + } + + /** + * {@inheritDoc} + */ + public Button createButton(Composite parent, String text) { + return this.createButton(parent, text, SWT.NULL); + } + + /** + * Creates a new button. + * + * @param parent The parent container + * @param text The button's text + * @param style The style to apply to the button, which determines its type: + * toggle, push, check box, radio + * @return The newly created <code>Button</code> + */ + private Button createButton(Composite parent, String text, int style) { + Button button = new Button(parent, style); + button.setText(text); + return button; + } + + /** + * {@inheritDoc} + */ + @Deprecated + public CCombo createCCombo(Composite parent) { + return new CCombo(parent, SWT.BORDER | SWT.READ_ONLY); + } + + /** + * {@inheritDoc} + */ + public Button createCheckBox(Composite parent, String text) { + return this.createButton(parent, text, SWT.CHECK); + } + + /** + * {@inheritDoc} + */ + public Combo createCombo(Composite parent) { + return new Combo(parent, SWT.BORDER | SWT.READ_ONLY); + } + + /** + * {@inheritDoc} + */ + public Composite createComposite(Composite parent) { + return new Composite(parent, SWT.NULL); + } + + /** + * {@inheritDoc} + */ + public DateTime createDateTime(Composite parent, int style) { + return new DateTime(parent, style); + } + + /** + * {@inheritDoc} + */ + @Deprecated + public CCombo createEditableCCombo(Composite parent) { + return new CCombo(parent, SWT.BORDER); + } + + /** + * {@inheritDoc} + */ + public Combo createEditableCombo(Composite parent) { + return new Combo(parent, SWT.BORDER); + } + + /** + * {@inheritDoc} + */ + public Group createGroup(Composite parent, String title) { + Group group = new Group(parent, SWT.NULL); + group.setText(title); + return group; + } + + /** + * {@inheritDoc} + */ + public Hyperlink createHyperlink(Composite parent, String text) { + Hyperlink hyperlink = new Hyperlink(parent, SWT.NULL); + hyperlink.setText(text); + return hyperlink; + } + + /** + * {@inheritDoc} + */ + public Label createLabel(Composite parent, String labelText) { + Label label = new Label(parent, SWT.WRAP); + label.setText(labelText); + return label; + } + + /** + * {@inheritDoc} + */ + public List createList(Composite parent, int style) { + return new List(parent, SWT.BORDER | style); + } + + /** + * {@inheritDoc} + */ + public FormText createMultiLineLabel(Composite parent, String labelText) { + + Composite container = new Composite(parent, SWT.NONE); + + GridData gridData = new GridData(); + gridData.horizontalAlignment = GridData.FILL; + gridData.grabExcessHorizontalSpace = true; + container.setLayoutData(gridData); + + TableWrapLayout layout = new TableWrapLayout(); + layout.numColumns = 1; + layout.bottomMargin = 0; + layout.leftMargin = 0; + layout.rightMargin = 0; + layout.topMargin = 0; + container.setLayout(layout); + + FormToolkit widgetFactory = new FormToolkit(parent.getDisplay()); + FormText text = widgetFactory.createFormText(container, true); + text.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + text.setText(labelText, false, false); + + return text; + } + + /** + * {@inheritDoc} + */ + public Text createMultiLineText(Composite parent) { + return new Text(parent, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL); + } + + /** + * {@inheritDoc} + */ + public Text createPasswordText(Composite parent) { + return new Text(parent, SWT.BORDER | SWT.PASSWORD); + } + + /** + * {@inheritDoc} + */ + public Button createPushButton(Composite parent, String text) { + return this.createButton(parent, text, SWT.PUSH); + } + + /** + * {@inheritDoc} + */ + public Button createRadioButton(Composite parent, String text) { + return this.createButton(parent, text, SWT.RADIO); + } + + /** + * {@inheritDoc} + */ + public Section createSection(Composite parent, int style) { + return new Section(parent, style); + } + + /** + * {@inheritDoc} + */ + public Spinner createSpinner(Composite parent) { + return new Spinner(parent, SWT.NULL); + } + + /** + * {@inheritDoc} + */ + public Table createTable(Composite parent, int style) { + return new Table(parent, SWT.BORDER | style); + } + + /** + * {@inheritDoc} + */ + public Text createText(Composite parent) { + return new Text(parent, SWT.BORDER); + } + + /** + * {@inheritDoc} + */ + public Button createTriStateCheckBox(Composite parent, String text) { + TriStateCheckBox checkBox = new TriStateCheckBox(parent, text, this); + return checkBox.getCheckBox(); + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/Dialog.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/Dialog.java new file mode 100644 index 0000000000..e68c992ed9 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/Dialog.java @@ -0,0 +1,350 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jpt.common.ui.internal.util.SWTUtil; +import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel; +import org.eclipse.jpt.utility.internal.node.Node; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.help.IWorkbenchHelpSystem; + +/** + * The abstract implementation of a dialog using a "state object" (model object) + * for behavior. + * <p> + * The main pane of this dialog should be extending <code>DialogPane</code> + * for creating the right type of widgets and it has the "state object" (subject) + * behavior built-in. + * + * @see Node + * @see DialogPane + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public abstract class Dialog<T extends Node> extends TitleAreaDialog +{ + /** + * The main content pane of this dialog. + */ + private DialogPane<?> pane; + + /** + * The holder of the "state object" used by this dialog. + */ + private WritablePropertyValueModel<T> subjectHolder; + + /** + * Caches the title text until the dialog is created and the dialog's shell + * needs to be configured. + */ + private String title; + + /** + * Creates a new <code>Dialog</code>. + * + * @param parent The parent shell + */ + protected Dialog(Shell parent) { + this(parent, ""); + } + + /** + * Creates a new <code>Dialog</code>. + * + * @param parent The parent shell + * @param title The dialog's title + */ + protected Dialog(Shell parent, String title) { + super(parent); + this.title = title; + initialize(); + } + + /** + * Initializes the main pane of this dialog. This method is invoked only + * when the dialog is requested to show on screen and not during + * initialization. + * + * @param container The container to which the widgets should be added to, + * the layout is already set + */ + protected abstract DialogPane<?> buildLayout(Composite container); + + /** + * Creates the state object (model object) that will be used to keep track + * of the information entered in this dialog. The state object will be stored + * in the subject holder and can be retrieved using {@link #subject()}. + * + * @return A new state object + */ + protected T buildStateObject() { + return null; + } + + /** + * Creates the <code>Validator</code> that will be notified when changes are + * made to the state object. + * + * @return The validator that will be set on the state object + */ + Node.Validator buildValidator() { + return Node.NULL_VALIDATOR; + } + + /* + * (non-Javadoc) + */ + @Override + public boolean close() { + + // Dispose the pane in order to remove any listeners that could + // have been installed outside the scrope of the state object + if (pane != null) { + pane.dispose(); + pane = null; + } + + return super.close(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText(getTitle()); + } + + /** + * {@inheritDoc} + */ + @Override + public void create() { + super.create(); + installSubject(); + } + + /* + * (non-Javadoc) + */ + @Override + protected Control createContents(Composite parent) { + if (hasTitleArea()) { + return super.createContents(parent); + } + + return createDefaultContent(parent); + } + + /** + * Creates the default main container of this dialog when the title area is + * not required. The top part is the dialog area populated by the subclass + * and the lower part is the button pane having the OK and Cancel buttons. + * + * @param parent The parent container + * @return The + */ + private Composite createDefaultContent(Composite parent) { + + Composite composite = new Composite(parent, SWT.NULL); + + GridLayout layout = new GridLayout(1, false); + layout.marginHeight = 0; + layout.marginWidth = 0; + layout.verticalSpacing = 0; + composite.setLayout(layout); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + + applyDialogFont(composite); + initializeDialogUnits(composite); + dialogArea = createDialogArea(composite); + buttonBar = createButtonBar(composite); + + return composite; + } + + /* + * (non-Javadoc) + */ + @Override + protected Composite createDialogArea(Composite parent) { + + // If the title area needs to be shown, then leave the superclass to + // create the necessary widgets + if (hasTitleArea()) { + parent = (Composite) super.createDialogArea(parent); + } + + // Create the main area's container + Composite container = new Composite(parent, SWT.NULL); + container.setLayout(new GridLayout(1, false)); + + GridData gridData = new GridData(); + gridData.horizontalAlignment = GridData.FILL; + gridData.verticalAlignment = GridData.FILL; + gridData.grabExcessHorizontalSpace = true; + gridData.grabExcessVerticalSpace = true; + container.setLayoutData(gridData); + + // Initialize the content pane + pane = buildLayout(container); + + // Initialize the UI part, which requires the widgets being created + initializeUI(); + + return parent; + } + + /** + * Determines whether the description area (where a title, description and + * image) should be visible or hidden. <code>ValidatingDialog</code> + * automatically show the description area in order to show problems. + * + * @return <code>false</code> by default, which means the methods used to + * update the title, description and image shouldn't be called; <code>true</code> + * to make the description pane visible + */ + protected boolean hasTitleArea() { + return false; + } + + /** + * Returns the helps system. + * + * @return The platform's help system + * + * @category Helper + */ + protected final IWorkbenchHelpSystem getHelpSystem() { + return PlatformUI.getWorkbench().getHelpSystem(); + } + + /** + * Initializes this dialog. + */ + protected void initialize() { + this.subjectHolder = new SimplePropertyValueModel<T>(); + } + + /** + * Initializes the UI part of this dialog, this is called after the widgets + * have been created. + */ + protected void initializeUI() { + } + + /** + * Creates the state object, if one is needed and install a <code>Validator</code> + * in order to receive notification of changes done to that state object. The + * subject can be retrieved from the subject holder. + */ + private void installSubject() { + + T subject = buildStateObject(); + + if (subject != null) { + subject.setValidator(buildValidator()); + } + + subjectHolder.setValue(subject); + } + + /** + * Asynchronously launches this dialog in the UI thread. + */ + public final void openDialog() { + SWTUtil.setUserInterfaceActive(false); + SWTUtil.show(this); + } + + /** + * Asynchronously launches this dialog in the UI thread and invoke the given + * <code>PostExecution</code> to perform any post-task. + * + * @param postExecution This interface let the caller to invoke a piece of + * code once the dialog is disposed + */ + public final void openDialog(PostExecution<? extends Dialog<T>> execution) { + SWTUtil.setUserInterfaceActive(false); + SWTUtil.show(this, execution); + } + + /** + * Gives access to the dialog's main pane. + * + * @return The pane showing the custom widgets + */ + protected DialogPane<?> getPane() { + return pane; + } + + /** + * Returns the subject of this dialog. + * + * @return The subject of this dialog or <code>null</code> if no subject was + * used + */ + public T getSubject() { + return subjectHolder.getValue(); + } + + /** + * Returns the holder of the subject. + * + * @return The subject holder used to be passed to the dialog pane, which is + * an instance of <code>DialogPane</code> + */ + protected final PropertyValueModel<T> getSubjectHolder() { + return subjectHolder; + } + + /** + * Retrieves the dialog's title. The title passed to the constructor will be + * returned by default but if it wasn't specified, this method can be used + * to return it. + * + * @return Either the title passed to the constructor or a different title + */ + protected String getTitle() { + return title; + } + + /** + * Determines whether the dialog was cancelled or not. + * + * @return <code>true</code> if the dialog was cancelled; <code>false</code> + * if it was confirmed + */ + public final boolean wasCancelled() { + return getReturnCode() == CANCEL; + } + + /** + * Determines whether the dialog was confirmed or not. + * + * @return <code>true</code> if the dialog was confirmed; <code>false</code> + * if it was cancelled + */ + public final boolean wasConfirmed() { + return getReturnCode() == OK; + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/DialogPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/DialogPane.java new file mode 100644 index 0000000000..7d041c8240 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/DialogPane.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jpt.utility.internal.node.Node; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.widgets.Composite; + +/** + * The abstract pane to use when the pane is shown in an <code>Dialog</code>. + * + * @see Dialog + * + * @version 2.0 + * @since 2.0 + */ +public abstract class DialogPane<T extends Node> extends Pane<T> { + + /** + * Creates a new <code>DialogPane</code>. + * + * @param parentPane The parent controller of this one + * @param parent The parent container + * + * @category Constructor + */ + protected DialogPane(DialogPane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + /** + * Creates a new <code>DialogPane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + * @param automaticallyAlignWidgets <code>true</code> to make the widgets + * this pane aligned with the widgets of the given parent controller; + * <code>false</code> to not align them + * + * @category Constructor + */ + protected DialogPane(DialogPane<? extends T> parentPane, + Composite parent, + boolean automaticallyAlignWidgets) { + + super(parentPane, parent, automaticallyAlignWidgets); + } + + /** + * Creates a new <code>DialogPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + * + * @category Constructor + */ + protected DialogPane(DialogPane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + /** + * Creates a new <code>DialogPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + * @param automaticallyAlignWidgets <code>true</code> to make the widgets + * this pane aligned with the widgets of the given parent controller; + * <code>false</code> to not align them + * + * @category Constructor + */ + protected DialogPane(DialogPane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + boolean automaticallyAlignWidgets) { + + super(parentPane, subjectHolder, parent, automaticallyAlignWidgets); + } + + /** + * Creates a new <code>DialogPane</code>. + * + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + * + * @category Constructor + */ + protected DialogPane(PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(subjectHolder, parent, DefaultWidgetFactory.instance()); + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumComboViewer.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumComboViewer.java new file mode 100644 index 0000000000..8cd31f08ef --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumComboViewer.java @@ -0,0 +1,369 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import java.util.Arrays; +import java.util.Comparator; +import org.eclipse.jface.viewers.ComboViewer; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jpt.common.ui.WidgetFactory; +import org.eclipse.jpt.common.ui.internal.JptCommonUiMessages; +import org.eclipse.jpt.common.ui.internal.util.SWTUtil; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.widgets.Composite; +import com.ibm.icu.text.Collator; + +/** + * This pane simply shows a combo where its data is populating through + * {@link #choices()} and a default value can also be added. + * <p> + * Here the layout of this pane: + * <pre> + * ----------------------------------------------------------------------------- + * | ------------------------------------------------------------------------- | + * | | I |v| | + * | ------------------------------------------------------------------------- | + * -----------------------------------------------------------------------------</pre> + * + * @version 2.0 + * @since 1.0 + */ +@SuppressWarnings("nls") +abstract class EnumComboViewer<T extends Model, V> extends Pane<T> +{ + /** + * The main widget of this pane. + */ + private ComboViewer comboViewer; + + /** + * A constant used to represent the <code>null</code> value. + */ + public static final String NULL_VALUE = "null"; + + /** + * Creates a new <code>EnumComboViewer</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + */ + EnumComboViewer(Pane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + /** + * Creates a new <code>EnumComboViewer</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + */ + EnumComboViewer(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + /** + * Creates a new <code>EnumComboViewer</code>. + * + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + */ + EnumComboViewer(PropertyValueModel<? extends T> subjectHolder, + Composite parent, + WidgetFactory widgetFactory) { + + super(subjectHolder, parent, widgetFactory); + } + + /** + * Creates the list of choices and add an extra element that represents the + * default value. + * + * @return The combo's choices including the default value + */ + private Object[] buildChoices() { + V[] choices = getChoices(); + if (sortChoices()) { + Arrays.sort(choices, buildComparator()); + } + + Object[] extendedChoices = new Object[choices.length + 1]; + System.arraycopy(choices, 0, extendedChoices, 1, choices.length); + extendedChoices[0] = NULL_VALUE; + + return extendedChoices; + } + + /** + * Return true to sort the choices in alphabetical order + * @return + */ + protected boolean sortChoices() { + return true; + } + + /** + * Creates the <code>ComboViewer</code> with the right combo widgets. + * + * @param container The container of the combo + * @return A new <code>ComboViewer</code> containing the right combo widget + */ + protected ComboViewer addComboViewer(Composite container) { + return addComboViewer(container, buildLabelProvider()); + } + + private Comparator<Object> buildComparator() { + return new Comparator<Object>() { + final LabelProvider labelProvider = buildLabelProvider(); + + public int compare(Object value1, Object value2) { + String displayString1 = labelProvider.getText(value1); + String displayString2 = labelProvider.getText(value2); + return Collator.getInstance().compare(displayString1, displayString2); + } + }; + } + + /** + * Retrieves the localized string from the given NLS class by creating the + * key. That key is the concatenation of the composite's short class name + * with the toString() of the given value separated by an underscore. + * + * @param nlsClass The NLS class used to retrieve the localized text + * @param compositeClass The class used for creating the key, its short class + * name is the beginning of the key + * @param value The value used to append its toString() to the generated key + * @return The localized text associated with the value + */ + protected final String buildDisplayString(Class<?> nlsClass, + Class<?> compositeClass, + Object value) { + + return SWTUtil.buildDisplayString(nlsClass, compositeClass, value); + } + + /** + * Retrieves the localized string from the given NLS class by creating the + * key. That key is the concatenation of the composite's short class name + * with the toString() of the given value separated by an underscore. + * + * @param nlsClass The NLS class used to retrieve the localized text + * @param composite The object used to retrieve the short class name that is + * the beginning of the key + * @param value The value used to append its toString() to the generated key + * @return The localized text associated with the value + */ + protected final String buildDisplayString(Class<?> nlsClass, + Object composite, + Object value) { + + return SWTUtil.buildDisplayString(nlsClass, composite, value); + } + + /** + * Creates the display string for the given element. If the element is the + * virtual <code>null</code> value then its display string will be "Default" + * appended by the actual default value, if it exists. + * + * @param value The value to convert into a human readable string + * @return The string representation of the given element + */ + @SuppressWarnings("unchecked") + private String buildDisplayString(Object value) { + if (value == NULL_VALUE) { + V defaultValue = (getSubject() != null) ? getDefaultValue() : null; + + if (defaultValue != null) { + String displayString = displayString(defaultValue); + return NLS.bind(JptCommonUiMessages.EnumComboViewer_defaultWithDefault, displayString); + } + return nullDisplayString(); + } + + return displayString((V) value); + } + + final LabelProvider buildLabelProvider() { + return new LabelProvider() { + @Override + public String getText(Object element) { + return buildDisplayString(element); + } + }; + } + + private ISelection buildSelection() { + Object value = (getSubject() != null) ? getValue() : null; + + if (value == null) { + value = NULL_VALUE; + } + + return new StructuredSelection(value); + } + + private ISelectionChangedListener buildSelectionChangedListener() { + return new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent e) { + if (!isPopulating()) { + StructuredSelection selection = (StructuredSelection) e.getSelection(); + valueChanged(selection.getFirstElement()); + } + } + }; + } + + /** + * Returns the possible choices to show in the viewer. + * + * @return The items to show in the combos + */ + protected abstract V[] getChoices(); + + /** + * Returns the default value, this method is not called if the subject is + * <code>null</code>. + * + * @return The value that is declared as being the default when it is not + * defined or <code>null</code> if there is no default value + */ + protected abstract V getDefaultValue(); + + /** + * Returns the displayable string for the given value. + * + * @param value The value to translate into a human readable string + * @return The localized text representing the given value + */ + protected abstract String displayString(V value); + + /** + * Returns the displayable string for a null value. + */ + protected String nullDisplayString() { + return null; //I would rather display nothing than "Default()" + } + + /* + * (non-Javadoc) + */ + @Override + protected void doPopulate() { + super.doPopulate(); + this.populateCombo(); + } + + /** + * Returns + * + * @return + */ + final ComboViewer getComboViewer() { + return comboViewer; + } + + /** + * Retrieves the subject's value. The subject is never <code>null</code>. + * + * @return The subject' value, which can be <code>null</code> + */ + protected abstract V getValue(); + + /* + * (non-Javadoc) + */ + @Override + protected final void initializeLayout(Composite container) { + + this.comboViewer = this.addComboViewer(container); + this.comboViewer.addSelectionChangedListener(buildSelectionChangedListener()); + } + + /** + * Populates the combo by re-adding all the items. + */ + private void populateCombo() { + + removeAll(); + comboViewer.add(buildChoices()); + updateSelection(); + } + + /* + * (non-Javadoc) + */ + @Override + protected void propertyChanged(String propertyName) { + super.propertyChanged(propertyName); + this.populateCombo(); + } + + /** + * Removes all the items from the combo. + */ + abstract void removeAll(); + + /** + * Requests the given new value be set on the subject. + * + * @param value The new value to be set + */ + protected abstract void setValue(V value); + + /** + * Updates the cursor, which is required to show the entire selected item + * within the combo's area. + */ + abstract void updateCursor(); + + /** + * Updates the combo's selected item. + */ + private void updateSelection() { + comboViewer.setSelection(buildSelection()); + updateCursor(); + } + + /** + * The selection changes, notify the subclass to set the value. + * + * @param value The new selected item + */ + @SuppressWarnings("unchecked") + private void valueChanged(Object value) { + + // Convert the default "null" value to a real null + if (value == NULL_VALUE) { + value = null; + } + + setPopulating(true); + + try { + setValue((V) value); + } + finally { + setPopulating(false); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumDialogComboViewer.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumDialogComboViewer.java new file mode 100644 index 0000000000..8cd752ac61 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumDialogComboViewer.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; + +/** + * This <code>EnumComboViewer</code> should be used within a dialog pane. + * + * @version 2.0 + * @since 2.0 + */ +public abstract class EnumDialogComboViewer<T extends Model, V> + extends EnumComboViewer<T, V> +{ + /** + * Creates a new <code>EnumDialogComboViewer</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + */ + protected EnumDialogComboViewer(DialogPane<? extends T> parentPane, + Composite parent + ) { + super(parentPane, parent); + } + + /** + * Creates a new <code>EnumDialogComboViewer</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + */ + protected EnumDialogComboViewer(DialogPane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent + ) { + super(parentPane, subjectHolder, parent); + } + + @Override + public void enableWidgets(boolean enabled) { + super.enableWidgets(enabled); + + Combo combo = getCombo(); + if ( ! combo.isDisposed()) { + combo.setEnabled(enabled); + } + } + + protected final Combo getCombo() { + return getComboViewer().getCombo(); + } + + @Override + void removeAll() { + getCombo().removeAll(); + } + + @Override + void updateCursor() { + getCombo().setSelection(new Point(0, 0)); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumFormComboViewer.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumFormComboViewer.java new file mode 100644 index 0000000000..c79501c0af --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumFormComboViewer.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; + +/** + * This <code>EnumComboViewer</code> should be used within a form pane. + * + * @version 2.3 + * @since 1.0 + */ +public abstract class EnumFormComboViewer<T extends Model, V> + extends EnumComboViewer<T, V> +{ + /** + * Creates a new <code>EnumFormComboViewer</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + */ + protected EnumFormComboViewer(Pane<? extends T> parentPane, + Composite parent + ) { + super(parentPane, parent); + } + + /** + * Creates a new <code>EnumFormComboViewer</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + */ + protected EnumFormComboViewer(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent + ) { + super(parentPane, subjectHolder, parent); + } + + @Override + public void enableWidgets(boolean enabled) { + super.enableWidgets(enabled); + + Combo combo = getCombo(); + if ( ! combo.isDisposed()) { + combo.setEnabled(enabled); + } + } + + protected final Combo getCombo() { + return this.getComboViewer().getCombo(); + } + + @Override + void removeAll() { + getCombo().removeAll(); + } + + @Override + void updateCursor() { + getCombo().setSelection(new Point(0, 0)); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FileChooserComboPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FileChooserComboPane.java new file mode 100644 index 0000000000..bae31806c6 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FileChooserComboPane.java @@ -0,0 +1,105 @@ +/******************************************************************************* +* Copyright (c) 2010 Oracle. 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: +* Oracle - initial API and implementation +*******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jpt.utility.internal.StringConverter; +import org.eclipse.jpt.utility.internal.model.value.SimpleListValueModel; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * This chooser allows the user to choose a file when browsing. + * <p> + * Here the layout of this pane: + * <pre> + * ----------------------------------------------------------------------------- + * | ---------------------------------------------------- ------------- | + * | Label: | I |v| | | Browse... | | + * | ---------------------------------------------------- ------------- | + * -----------------------------------------------------------------------------</pre> + * + * @version 3.0 + * @since 3.0 + */ +public abstract class FileChooserComboPane<T extends Model> extends FileChooserPane<T> +{ + /** + * Creates a new <code>FileChooserComboPane</code>. + * + * @param parentPane The parent pane of this one + * @param parent The parent container + */ + public FileChooserComboPane(Pane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + /** + * Creates a new <code>FileChooserComboPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + */ + public FileChooserComboPane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + @Override + protected Control addMainControl(Composite container) { + + return addEditableCombo( + container, + this.buildListHolder(), + this.getTextHolder(), + this.buildStringConverter() + ); + } + + /** + * Creates the list holder of the combo box. + */ + protected ListValueModel<String> buildListHolder() { + return new SimpleListValueModel<String>( + this.buildDefaultList() + ); + } + + /** + * Creates the default list of the combo box. + */ + protected List<String> buildDefaultList() { + return Arrays.asList(this.getDefaultString()); + } + + /** + * Returns the default value of the combo box. + */ + protected abstract String getDefaultString(); + + /** + * The converter responsible to transform each combo box item + * into a string representation + */ + protected StringConverter<String> buildStringConverter() { + return StringConverter.Default.<String>instance(); + } + +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FileChooserPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FileChooserPane.java new file mode 100644 index 0000000000..98b2063f06 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FileChooserPane.java @@ -0,0 +1,167 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.jpt.common.ui.JptCommonUiPlugin; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.ui.dialogs.ISelectionStatusValidator; + +/** + * This chooser allows the user to choose a file when browsing. + * <p> + * Here the layout of this pane: + * <pre> + * ----------------------------------------------------------------------------- + * | ---------------------------------------------------- ------------- | + * | Label: | I | | Browse... | | + * | ---------------------------------------------------- ------------- | + * -----------------------------------------------------------------------------</pre> + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public abstract class FileChooserPane<T extends Model> extends ChooserPane<T> +{ + private WritablePropertyValueModel<String> textHolder; + + /** + * Creates a new <code>FileChooserPane</code>. + * + * @param parentPane The parent pane of this one + * @param parent The parent container + */ + public FileChooserPane(Pane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + /** + * Creates a new <code>FileChooserPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + */ + public FileChooserPane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + @Override + protected final Runnable buildBrowseAction() { + return new Runnable() { + public void run() { + promptFile(); + } + }; + } + + /** + * Creates the <code>ViewerFilter</code> that will filter the content of the + * dialog and only displays what is valid. + * + * @return A new <code>ViewerFilter</code> + */ + protected ViewerFilter buildFilter() { + return new ViewerFilter() { + @Override + public boolean select(Viewer viewer, + Object parentElement, + Object element) { + + return true; + } + }; + } + + @Override + protected Control addMainControl(Composite container) { + return this.addText(container, this.textHolder); + } + + /** + * Creates the value holder of the subject's property. + * + * @return The holder of the class name + */ + protected abstract WritablePropertyValueModel<String> buildTextHolder(); + + /** + * Creates the validator that will show a status message based on what is + * selected in the selection dialog. + * + * @return A new <code>ISelectionStatusValidator</code> + */ + protected ISelectionStatusValidator buildValidator() { + return new ISelectionStatusValidator() { + public IStatus validate(Object[] selection) { + + if (selection.length != 1) { + return new Status(IStatus.ERROR, JptCommonUiPlugin.PLUGIN_ID, ""); + } + + return new Status(IStatus.OK, JptCommonUiPlugin.PLUGIN_ID, ""); + } + }; + } + + /** + * Returns the selection dialog's title. + * + * @return A non-<code>null</code> string + */ + protected abstract String getDialogTitle(); + + /** + * Retrieves the project path that will be used by the selection dialog. + * + * @return The project path used to display its content in a selection dialog + */ + protected abstract String getProjectPath(); + + protected WritablePropertyValueModel<String> getTextHolder() { + return this.textHolder; + } + + @Override + protected void initialize() { + super.initialize(); + this.textHolder = this.buildTextHolder(); + } + + /** + * The browse button was clicked, its action invokes this action which should + * prompt the user to select a file and set it. + */ + protected void promptFile() { + String projectPath= this.getProjectPath(); + + FileDialog dialog = new FileDialog(getShell()); + dialog.setText(this.getDialogTitle()); + dialog.setFilterPath(projectPath); + String filePath = dialog.open(); + if (filePath != null) { + FileChooserPane.this.textHolder.setValue(filePath); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FolderChooserComboPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FolderChooserComboPane.java new file mode 100644 index 0000000000..ec4e55cb2d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FolderChooserComboPane.java @@ -0,0 +1,106 @@ +/******************************************************************************* +* Copyright (c) 2010 Oracle. 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: +* Oracle - initial API and implementation +*******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jpt.utility.internal.StringConverter; +import org.eclipse.jpt.utility.internal.model.value.SimpleListValueModel; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + + +/** + * This chooser allows the user to choose a folder when browsing. + * <p> + * Here the layout of this pane: + * <pre> + * ----------------------------------------------------------------------------- + * | ---------------------------------------------------- ------------- | + * | Label: | I |v| | | Browse... | | + * | ---------------------------------------------------- ------------- | + * -----------------------------------------------------------------------------</pre> + * + * @version 3.0 + * @since 3.0 + */ +public abstract class FolderChooserComboPane<T extends Model> extends FolderChooserPane<T> +{ + /** + * Creates a new <code>FolderChooserComboPane</code>. + * + * @param parentPane The parent pane of this one + * @param parent The parent container + */ + public FolderChooserComboPane(Pane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + /** + * Creates a new <code>FolderChooserComboPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + */ + public FolderChooserComboPane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + @Override + protected Control addMainControl(Composite container) { + + return addEditableCombo( + container, + this.buildListHolder(), + this.getTextHolder(), + this.buildStringConverter() + ); + } + + /** + * Creates the list holder of the combo box. + */ + protected ListValueModel<String> buildListHolder() { + return new SimpleListValueModel<String>( + this.buildDefaultList() + ); + } + + /** + * Creates the default list of the combo box. + */ + protected List<String> buildDefaultList() { + return Arrays.asList(this.getDefaultString()); + } + + /** + * Returns the default value of the combo box. + */ + protected abstract String getDefaultString(); + + /** + * The converter responsible to transform each combo box item + * into a string representation + */ + protected StringConverter<String> buildStringConverter() { + return StringConverter.Default.<String>instance(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FolderChooserPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FolderChooserPane.java new file mode 100644 index 0000000000..b9f3fd4448 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FolderChooserPane.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2008, 20010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.DirectoryDialog; + +/** + * This chooser allows the user to choose a folder when browsing. + * <p> + * Here the layout of this pane: + * <pre> + * ----------------------------------------------------------------------------- + * | ---------------------------------------------------- ------------- | + * | Label: | I | | Browse... | | + * | ---------------------------------------------------- ------------- | + * -----------------------------------------------------------------------------</pre> + * + * @version 3.0 + * @since 2.0 + */ +public abstract class FolderChooserPane<T extends Model> extends ChooserPane<T> +{ + private WritablePropertyValueModel<String> textHolder; + + /** + * Creates a new <code>FolderChooserPane</code>. + * + * @param parentPane The parent pane of this one + * @param parent The parent container + */ + public FolderChooserPane(Pane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + /** + * Creates a new <code>FolderChooserPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + */ + public FolderChooserPane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + @Override + protected final Runnable buildBrowseAction() { + return new Runnable() { + public void run() { + promptFolder(); + } + }; + } + + @Override + protected Control addMainControl(Composite container) { + return this.addText(container, this.textHolder); + } + + /** + * Creates the value holder of the subject's property. + * + * @return The holder of the class name + */ + protected abstract WritablePropertyValueModel<String> buildTextHolder(); + + /** + * Returns the message to be shown in the selection dialog. + * + * @return A non-<code>null</code> string shown above the text field of the + * selection dialog + */ + protected abstract String getDialogMessage(); + + /** + * Returns the selection dialog's title. + * + * @return A non-<code>null</code> string + */ + protected abstract String getDialogTitle(); + + /** + * Returns the path that the dialog will use to filter the directories it + * shows to the argument, which may be null. If the string is null, then the + * operating system's default filter path will be used. + * <p> + * Note that the path string is platform dependent. For convenience, either + * '/' or '\' can be used as a path separator. + * </p> + * + * @return The filter path + */ + protected String filterPath() { + return null; + } + + protected WritablePropertyValueModel<String> getTextHolder() { + return this.textHolder; + } + + @Override + protected void initialize() { + super.initialize(); + this.textHolder = this.buildTextHolder(); + } + + /** + * The browse button was clicked, its action invokes this action which should + * prompt the user to select a folder and set it. + */ + protected void promptFolder() { + + DirectoryDialog dialog = new DirectoryDialog(getShell()); + dialog.setMessage(this.getDialogMessage()); + dialog.setText(this.getDialogTitle()); + dialog.setFilterPath(this.filterPath()); + String directory = dialog.open(); + + if (directory != null) { + this.textHolder.setValue(directory); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FormWidgetFactory.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FormWidgetFactory.java new file mode 100644 index 0000000000..ea1302f7df --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FormWidgetFactory.java @@ -0,0 +1,338 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jpt.common.ui.WidgetFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CCombo; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.DateTime; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.List; +import org.eclipse.swt.widgets.Spinner; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Hyperlink; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.eclipse.ui.forms.widgets.TableWrapLayout; + +/** + * This <code>WidgetFactory</code> is responsible to create the widgets + * using the <code>FormToolkit</code> in order use the form style (flat-style) + * look and feel. + * + * @see FormToolkit + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public class FormWidgetFactory implements WidgetFactory { + + /** + * The actual factory responsible for creating the new widgets. + */ + private final FormToolkit widgetFactory; + + /** + * Creates a new <code>FormWidgetFactory</code>. + * + * @param widgetFactory The actual factory responsible for creating the new + * widgets + */ + public FormWidgetFactory(FormToolkit widgetFactory) { + super(); + + Assert.isNotNull(widgetFactory, "The widget factory cannot be null"); + this.widgetFactory = widgetFactory; + } + + /** + * Wraps the given <code>Composite</code> into a new <code>Composite</code> + * in order to have the widgets' border painted. Except for <code>CCombo</code>, + * the top and bottom margins have to be 2 pixel and the left and right + * margins have to be 1 pixel. + * + * @param container The parent of the sub-pane + * @return A new <code>Composite</code> that has the necessary space to paint + * the border + */ + protected Composite createBorderContainer(Composite container) { + return createBorderContainer(container, 2, 1); + } + + protected Composite createBorderContainer(Composite container, int marginHeight, int marginWidth) { + + GridLayout layout = new GridLayout(1, false); + layout.marginHeight = marginHeight; + layout.marginWidth = marginWidth; + + GridData gridData = new GridData(); + gridData.horizontalAlignment = GridData.FILL; + gridData.grabExcessHorizontalSpace = true; + + container = widgetFactory.createComposite(container); + container.setLayoutData(gridData); + container.setLayout(layout); + + return container; + } + + /** + * {@inheritDoc} + */ + public Button createButton(Composite parent, String text) { + return createButton(parent, text, SWT.NULL); + } + + /** + * Creates a new button. + * + * @param parent The parent container + * @param text The button's text + * @param style The style to apply to the button, which determines its type: + * toggle, push, check box, radio + * @return The newly created <code>Button</code> + */ + protected Button createButton(Composite parent, String text, int style) { + return widgetFactory.createButton(parent, text, SWT.FLAT | style); + } + + /** + * {@inheritDoc} + */ + @Deprecated + public CCombo createCCombo(Composite parent) { + return createCCombo(parent, SWT.READ_ONLY); + } + + /** + * Creates a new combo. + * + * @param parent The parent container + * @param style The style to apply to the combo, usually read-only, flat + * @return The newly created <code>CCombo</code> + */ + protected CCombo createCCombo(Composite parent, int style) { + parent = createBorderContainer(parent, 1, 1); + + CCombo combo = new CCombo(parent, style); + widgetFactory.adapt(combo, true, false); + + // Bugzilla 145837 - workaround for no borders on Windows XP + if (widgetFactory.getBorderStyle() == SWT.BORDER) { + combo.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER); + } + + return combo; + } + + /** + * {@inheritDoc} + */ + public Button createCheckBox(Composite parent, String text) { + return createButton(parent, text, SWT.CHECK); + } + + /** + * {@inheritDoc} + */ + public Combo createCombo(Composite parent) { + return new Combo(parent, SWT.READ_ONLY | SWT.FLAT); + } + + /** + * {@inheritDoc} + */ + public Composite createComposite(Composite parent) { + return this.widgetFactory.createComposite(parent); + } + /** + * {@inheritDoc} + */ + public DateTime createDateTime(Composite parent, int style) { + parent = createBorderContainer(parent); + + DateTime dateTime = new DateTime(parent, style | SWT.FLAT); + dateTime.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER); + this.widgetFactory.adapt(dateTime, true, false); + + return dateTime; + } + + /** + * {@inheritDoc} + */ + @Deprecated + public CCombo createEditableCCombo(Composite parent) { + return createCCombo(parent, SWT.NULL); + } + + /** + * {@inheritDoc} + */ + public Combo createEditableCombo(Composite parent) { + Combo combo = new Combo(parent, SWT.FLAT); + return combo; + } + + /** + * {@inheritDoc} + */ + public Group createGroup(Composite parent, String title) { + Group group = new Group(parent, SWT.NULL); + group.setText(title); + return group; + } + + /** + * {@inheritDoc} + */ + public Hyperlink createHyperlink(Composite parent, String text) { + return widgetFactory.createHyperlink(parent, text, SWT.FLAT); + } + + /** + * {@inheritDoc} + */ + public Label createLabel(Composite container, String labelText) { + return widgetFactory.createLabel(container, labelText, SWT.WRAP); + } + + /** + * {@inheritDoc} + */ + public List createList(Composite container, int style) { + List list = new List(container, SWT.FLAT | style); + list.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER); + return list; + } + + /** + * {@inheritDoc} + */ + public FormText createMultiLineLabel(Composite parent, String labelText) { + + Composite container = widgetFactory.createComposite(parent, SWT.NONE); + + GridData gridData = new GridData(); + gridData.horizontalAlignment = GridData.FILL; + gridData.grabExcessHorizontalSpace = true; + container.setLayoutData(gridData); + + TableWrapLayout layout = new TableWrapLayout(); + layout.numColumns = 1; + layout.bottomMargin = 0; + layout.leftMargin = 0; + layout.rightMargin = 0; + layout.topMargin = 0; + container.setLayout(layout); + + FormText text = widgetFactory.createFormText(container, true); + text.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + text.setText(labelText, false, false); + + return text; + } + + /** + * {@inheritDoc} + */ + public Text createMultiLineText(Composite parent) { + return createText(parent, SWT.MULTI | SWT.V_SCROLL); + } + + /** + * {@inheritDoc} + */ + public Text createPasswordText(Composite parent) { + return createText(parent, SWT.PASSWORD); + } + + /** + * {@inheritDoc} + */ + public Button createPushButton(Composite parent, String text) { + return createButton(parent, text, SWT.PUSH); + } + + /** + * {@inheritDoc} + */ + public Button createRadioButton(Composite parent, String text) { + return createButton(parent, text, SWT.RADIO); + } + + /** + * {@inheritDoc} + */ + public Section createSection(Composite parent, int style) { + return widgetFactory.createSection(parent, SWT.FLAT | style); + } + + /** + * {@inheritDoc} + */ + public Spinner createSpinner(Composite parent) { + parent = createBorderContainer(parent); + + Spinner spinner = new Spinner(parent, SWT.FLAT); + spinner.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER); + widgetFactory.adapt(spinner, true, false); + + return spinner; + } + + /** + * {@inheritDoc} + */ + public Table createTable(Composite parent, int style) { + Table table = this.widgetFactory.createTable(parent, SWT.BORDER | style); + table.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER); + return table; + } + + /** + * {@inheritDoc} + */ + public Text createText(Composite parent) { + return createText(parent, SWT.NONE); + } + + protected Text createText(Composite parent, int style) { + return widgetFactory.createText(parent, null, SWT.BORDER | SWT.FLAT | style); + } + + /** + * {@inheritDoc} + */ + public Button createTriStateCheckBox(Composite parent, String text) { + TriStateCheckBox checkBox = new TriStateCheckBox(parent, text, this); + return checkBox.getCheckBox(); + } + + /** + * Returns the actual factory responsible for creating the new widgets. + * + * @return The factory creating the widgets with the form style (flat-style) + */ + public FormToolkit getWidgetFactory() { + return widgetFactory; + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/IntegerCombo.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/IntegerCombo.java new file mode 100644 index 0000000000..2f34008a21 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/IntegerCombo.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2009, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jface.fieldassist.FieldDecorationRegistry; +import org.eclipse.jpt.common.ui.internal.JptCommonUiMessages; +import org.eclipse.jpt.common.ui.internal.util.SWTUtil; +import org.eclipse.jpt.utility.internal.model.value.PropertyListValueModelAdapter; +import org.eclipse.jpt.utility.internal.model.value.TransformationPropertyValueModel; +import org.eclipse.jpt.utility.internal.model.value.TransformationWritablePropertyValueModel; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; + +/** + * This is a replacement for a Spinner. It is a combo that only accepts integers. + * It also includes a Default option in the combo. + */ +public abstract class IntegerCombo<T extends Model> + extends Pane<T> +{ + + /** + * The main (only) widget of this pane. + */ + private Combo comboBox; + + + private PropertyValueModel<String> defaultValueHolder; + + // ********** constructors ********** + + protected IntegerCombo( + Pane<? extends T> parentPane, + Composite parent + ) { + super(parentPane, parent); + } + + protected IntegerCombo( + Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent + ) { + super(parentPane, subjectHolder, parent); + } + + public Combo getCombo() { + return this.comboBox; + } + + // ********** initialization ********** + + @Override + protected void initializeLayout(Composite container) { + this.defaultValueHolder = buildDefaultStringHolder(); + this.comboBox = this.addIntegerCombo(container); + + int margin = FieldDecorationRegistry.getDefault().getMaximumDecorationWidth(); + GridData gridData = new GridData(); + gridData.horizontalAlignment = GridData.FILL_HORIZONTAL; + gridData.horizontalIndent = margin; + gridData.grabExcessHorizontalSpace = false; + this.comboBox.setLayoutData(gridData); + + this.comboBox.addVerifyListener(this.buildVerifyListener()); + SWTUtil.attachDefaultValueHandler(this.comboBox); + } + + protected Combo addIntegerCombo(Composite container) { + return this.addLabeledEditableCombo( + container, + getLabelText(), + buildDefaultListHolder(), + buildSelectedItemStringHolder(), + getHelpId() + ); + + } + + protected VerifyListener buildVerifyListener() { + return new VerifyListener() { + public void verifyText(VerifyEvent e) { + IntegerCombo.this.verifyComboBox(e); + } + }; + } + + protected ListValueModel<String> buildDefaultListHolder() { + return new PropertyListValueModelAdapter<String>(this.defaultValueHolder); + } + + private PropertyValueModel<String> buildDefaultStringHolder() { + return new TransformationPropertyValueModel<Integer, String>(buildDefaultHolder()) { + @Override + protected String transform(Integer value) { + if (value == null) { + return JptCommonUiMessages.NoneSelected; + } + return super.transform(value); + } + @Override + protected String transform_(Integer value) { + return getDefaultValueString(value); + } + }; + } + + private String getDefaultValueString(Integer defaultValue) { + return NLS.bind( + JptCommonUiMessages.DefaultWithOneParam, + defaultValue + ); + } + + private String getDefaultValueString() { + return this.defaultValueHolder.getValue(); + } + + protected WritablePropertyValueModel<String> buildSelectedItemStringHolder() { + return new TransformationWritablePropertyValueModel<Integer, String>(buildSelectedItemHolder()) { + @Override + protected String transform(Integer value) { + return value == null ? + getDefaultValueString() + : + value.toString(); + } + + @Override + protected Integer reverseTransform_(String value) { + int intLength; + try { + intLength = Integer.parseInt(value); + } + catch (NumberFormatException e) { + //if the default is selected from the combo, set length to null + return null; + } + return Integer.valueOf(intLength); + } + }; + } + + // ********** abstract methods ********** + + protected abstract String getLabelText(); + + protected abstract String getHelpId(); + + protected abstract PropertyValueModel<Integer> buildDefaultHolder(); + + protected abstract WritablePropertyValueModel<Integer> buildSelectedItemHolder(); + + // ********** combo-box verify listener callback ********** + + protected void verifyComboBox(VerifyEvent e) { + if (e.character == '\b') { + //ignore backspace + return; + } + if (e.text.equals("") //DefaultValueHandler sets the text to "" //$NON-NLS-1$ + || e.text.equals(this.defaultValueHolder.getValue())) { + return; + } + try { + Integer.parseInt(e.text); + } + catch (NumberFormatException exception) { + e.doit = false; + } + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameDialog.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameDialog.java new file mode 100644 index 0000000000..ea51d6209b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameDialog.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import java.util.Collection; +import org.eclipse.jpt.utility.internal.model.value.PropertyAspectAdapter; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * The dialog used to requests a name from the user. + * + * @version 2.0 + * @since 2.0 + */ +public class NewNameDialog extends ValidatingDialog<NewNameStateObject> +{ + private String description; + private Image descriptionImage; + private String descriptionTitle; + private String labelText; + private String name; + private Collection<String> names; + + /** + * Creates a new <code>NewNameDialog</code>. + * + * @param parentShell + * @param dialogTitle + * @param descriptionTitle + * @param descriptionImage + * @param description + * @param labelText + * @param name + * @param names + */ + NewNameDialog(Shell parentShell, + String dialogTitle, + String descriptionTitle, + Image descriptionImage, + String description, + String labelText, + String name, + Collection<String> names) + { + super(parentShell, dialogTitle); + + this.name = name; + this.names = names; + this.labelText = labelText; + this.description = description; + this.descriptionImage = descriptionImage; + this.descriptionTitle = descriptionTitle; + } + + /* + * (non-Javadoc) + */ + @Override + protected DialogPane<NewNameStateObject> buildLayout(Composite container) { + return new NewNameDialogPane(container); + } + + /* + * (non-Javadoc) + */ + @Override + protected NewNameStateObject buildStateObject() { + return new NewNameStateObject(name, names); + } + + /* + * (non-Javadoc) + */ + @Override + public void create() { + super.create(); + + NewNameDialogPane pane = (NewNameDialogPane) getPane(); + pane.selectAll(); + + getButton(OK).setEnabled(false); + } + + /* + * (non-Javadoc) + */ + @Override + protected String getDescription() { + return description; + } + + /* + * (non-Javadoc) + */ + @Override + protected Image getDescriptionImage() { + return descriptionImage; + } + + /* (non-Javadoc) + */ + @Override + protected String getDescriptionTitle() { + return descriptionTitle; + } + + /** + * Returns the text field's input, which is the new name the user entered. + * + * @return The name the user entered + */ + public String getName() { + return getSubject().getName(); + } + + private class NewNameDialogPane extends DialogPane<NewNameStateObject> { + + private Text text; + + NewNameDialogPane(Composite parent) { + super(NewNameDialog.this.getSubjectHolder(), parent); + } + + private WritablePropertyValueModel<String> buildNameHolder() { + return new PropertyAspectAdapter<NewNameStateObject, String>(getSubjectHolder(), NewNameStateObject.NAME_PROPERTY) { + @Override + protected String buildValue_() { + return subject.getName(); + } + + @Override + protected void setValue_(String value) { + subject.setName(value); + } + }; + } + + /* + * (non-Javadoc) + */ + @Override + protected void initializeLayout(Composite container) { + + text = addLabeledText( + container, + labelText, + buildNameHolder() + ); + } + + void selectAll() { + text.selectAll(); + } + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameDialogBuilder.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameDialogBuilder.java new file mode 100644 index 0000000000..55b884133b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameDialogBuilder.java @@ -0,0 +1,179 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jpt.utility.internal.CollectionTools; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Shell; + +/** + * This builder is responsible to create a fully initialized + * <code>NewNameDialog</code> once all the properties have been set. + * + * @see NewNameDialog + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public final class NewNameDialogBuilder { + + /** + * The message to show in the description area. + */ + private String description; + + /** + * The image of the description area. + */ + private Image descriptionImage; + + /** + * The title to show in the description area. + */ + private String descriptionTitle; + + /** + * The title of the new name dialog. + */ + private String dialogTitle; + + /** + * The text field's label. + */ + private String labelText; + + /** + * The initial input or <code>null</code> if no initial value can be + * specified. + */ + private String name; + + /** + * The collection of names that can't be used or an empty collection if none + * are available. + */ + private Collection<String> names; + + /** + * The parent shell of the new name dialog. + */ + private Shell parentShell; + + /** + * Creates a new <code>NewNameDialogBuilder</code>. + * + * @param parentShell The parent shell of the new name dialog + */ + public NewNameDialogBuilder(Shell parentShell) { + super(); + initialize(parentShell); + } + + /** + * Creates the dialog that will be used to request a new name from the user. + * + * @return The initialized dialog + */ + public NewNameDialog buildDialog() { + return new NewNameDialog( + parentShell, + dialogTitle, + descriptionTitle, + descriptionImage, + description, + labelText, + name, + names + ); + } + + /** + * Initializes this builder. + * + * @param parentShell The parent shell of the new name dialog + */ + protected void initialize(Shell parentShell) { + + Assert.isNotNull(parentShell, "The parent shell cannot be null"); + + this.parentShell = parentShell; + this.names = Collections.emptyList(); + } + + /** + * Sets the description to be shown in the description area under the title. + * + * @param description The message to show in the description area + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Sets the image to be shown to the right side of the description area. + * + * @param descriptionImage The image of the description area + */ + public void setDescriptionImage(Image descriptionImage) { + this.descriptionImage = descriptionImage; + } + + /** + * Sets the title to be shown in the description area. + * + * @param descriptionTitle The title to show in the description area + */ + public void setDescriptionTitle(String descriptionTitle) { + this.descriptionTitle = descriptionTitle; + } + + /** + * Sets the dialog's title. + * + * @param dialogTitle The title of the new name dialog + */ + public void setDialogTitle(String dialogTitle) { + this.dialogTitle = dialogTitle; + } + + /** + * Sets the existing names that will be used to validate the text field's + * input and prevent the user from using it. + * + * @param names The collection of names that can't be used + */ + public void setExistingNames(Iterator<String> names) { + this.names = CollectionTools.collection(names); + } + + /** + * Sets the text to label the text field. + * + * @param labelText The text field's label + */ + public void setLabelText(String labelText) { + this.labelText = labelText; + } + + /** + * Sets the initial name if one exists. It is valid to leave this + * <code>null</code> when the user has to enter something. + * + * @param name The initial input + */ + public void setName(String name) { + this.name = name; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameStateObject.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameStateObject.java new file mode 100644 index 0000000000..cd8fa6f7f4 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameStateObject.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import java.util.Collection; +import java.util.List; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jpt.common.ui.internal.JptCommonUiMessages; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.internal.node.AbstractNode; +import org.eclipse.jpt.utility.internal.node.Node; +import org.eclipse.jpt.utility.internal.node.Problem; + +/** + * This is the state object used by the <code>NewNameDialog</code>, which stores + * the current name and validates it when it is modified. + * + * @see NewNameDialog + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +final class NewNameStateObject extends AbstractNode +{ + /** + * The initial input or <code>null</code> if no initial value can be + * specified. + */ + private String name; + + /** + * The collection of names that can't be used or an empty collection if none + * are available. + */ + private Collection<String> names; + + /** + * The <code>Validator</code> used to validate this state object. + */ + private Validator validator; + + /** + * Notifies a change in the name property. + */ + static final String NAME_PROPERTY = "name"; + + /** + * Creates a new <code>NewNameStateObject</code>. + * + * @param name The initial input or <code>null</code> if no initial value can + * be specified + * @param names The collection of names that can't be used or an empty + * collection if none are available + */ + NewNameStateObject(String name, Collection<String> names) { + super(null); + + this.name = name; + this.names = names; + } + + /** + * Validates the name property. + * + * @param currentProblems The list to which <code>Problem</code>s can be + * added + */ + private void addNameProblems(List<Problem> currentProblems) { + + if (StringTools.stringIsEmpty(name)) { + currentProblems.add(buildProblem(JptCommonUiMessages.NewNameStateObject_nameMustBeSpecified, IMessageProvider.ERROR)); + } + else if (names.contains(name.trim())) { + currentProblems.add(buildProblem(JptCommonUiMessages.NewNameStateObject_nameAlreadyExists, IMessageProvider.ERROR)); + } + } + + /* + * (non-Javadoc) + */ + @Override + protected void addProblemsTo(List<Problem> currentProblems) + { + super.addProblemsTo(currentProblems); + addNameProblems(currentProblems); + } + + /* + * (non-Javadoc) + */ + @Override + protected void checkParent(Node parentNode) { + } + + /* + * (non-Javadoc) + */ + public String displayString() { + return null; + } + + /** + * Returns the current name stored in this state object. + * + * @return The current name or <code>null</code> + */ + String getName() { + return name; + } + + /** + * Sets the current name stored in this state object or <code>null</code> to + * clear it. + * + * @param name The new name or <code>null</code> + */ + public void setName(String name) { + String oldName = this.name; + this.name = name; + firePropertyChanged(NAME_PROPERTY, oldName, name); + } + + /* + * (non-Javadoc) + */ + @Override + public void setValidator(Validator validator) { + this.validator = validator; + } + + /* + * (non-Javadoc) + */ + @Override + public Validator getValidator() { + return validator; + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NullPostExecution.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NullPostExecution.java new file mode 100644 index 0000000000..21e7420523 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NullPostExecution.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jface.dialogs.Dialog; + +/** + * A <code>null</code> instance of <code>PostExecution</code>. + * + * @version 2.0 + * @since 1.0 + */ +public final class NullPostExecution implements PostExecution<Dialog> { + + /** + * The singleton instance of this <code>NullPostExecution</code>. + */ + private static PostExecution<Dialog> INSTANCE; + + /** + * Creates a new <code>NullPostExecution</code>. + */ + private NullPostExecution() { + super(); + } + + /** + * Returns the singleton instance of this <code>NullPostExecution</code>. + * + * @param <T> The dialog where this <code>PostExecution</code> will be used + * @return The singleton instance with the proper type + */ + @SuppressWarnings("unchecked") + public static synchronized <T extends Dialog> PostExecution<T> instance() { + + if (INSTANCE == null) { + INSTANCE = new NullPostExecution(); + } + + return (PostExecution<T>) INSTANCE; + } + + /* + * (non-Javadoc) + */ + public void execute(Dialog dialog) { + // Nothing to do + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PackageChooserPane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PackageChooserPane.java new file mode 100644 index 0000000000..f88dfba108 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PackageChooserPane.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.ui.refactoring.contentassist.ControlContentAssistHelper; +import org.eclipse.jdt.internal.ui.refactoring.contentassist.JavaPackageCompletionProcessor; +import org.eclipse.jdt.ui.JavaElementLabelProvider; +import org.eclipse.jdt.ui.JavaUI; +import org.eclipse.jface.fieldassist.FieldDecorationRegistry; +import org.eclipse.jface.window.Window; +import org.eclipse.jpt.common.core.internal.utility.jdt.JDTTools; +import org.eclipse.jpt.common.ui.JptCommonUiPlugin; +import org.eclipse.jpt.common.ui.internal.JptCommonUiMessages; +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.SelectionDialog; + +/** + * This chooser allows the user to choose a package when browsing and it adds + * code completion support to the text field, which is the main component. + * <p> + * Here the layout of this pane: + * <pre> + * ----------------------------------------------------------------------------- + * | !---------------------------------------------------- ------------- | + * | Label: | I | | Browse... | | + * | ---------------------------------------------------- ------------- | + * -----------------------------------------------------------------------------</pre> + * + * @version 2.0 + * @since 2.0 + */ +public abstract class PackageChooserPane<T extends Model> extends ChooserPane<T> +{ + /** + * The code completion manager. + */ + private JavaPackageCompletionProcessor javaPackageCompletionProcessor; + + private PropertyChangeListener subjectChangeListener; + + /** + * Creates a new <code>PackageChooserPane</code>. + * + * @param parentPane The parent pane of this one + * @param parent The parent container + */ + public PackageChooserPane(Pane<? extends T> parentPane, + Composite parent) { + + super(parentPane, parent); + } + + /** + * Creates a new <code>PackageChooserPane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + */ + public PackageChooserPane(Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + super(parentPane, subjectHolder, parent); + } + + @Override + protected void initialize() { + super.initialize(); + + // TODO bug 156185 - when this is fixed there should be api for this + this.javaPackageCompletionProcessor = new JavaPackageCompletionProcessor( + new JavaElementLabelProvider(JavaElementLabelProvider.SHOW_ROOT) + ); + this.subjectChangeListener = this.buildSubjectChangeListener(); + this.getSubjectHolder().addPropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener); + this.packageChooserSubjectChanged(getSubject()); + } + + private PropertyChangeListener buildSubjectChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildSubjectChangeListener_()); + } + + private PropertyChangeListener buildSubjectChangeListener_() { + return new PropertyChangeListener() { + @SuppressWarnings("unchecked") + public void propertyChanged(PropertyChangeEvent e) { + PackageChooserPane.this.packageChooserSubjectChanged((T) e.getNewValue()); + } + }; + } + + protected void packageChooserSubjectChanged(T newSubject) { + IPackageFragmentRoot root = null; + if (newSubject != null) { + root = getPackageFragmentRoot(); + } + this.javaPackageCompletionProcessor.setPackageFragmentRoot(root); + } + + @Override + protected final Runnable buildBrowseAction() { + return new Runnable() { + public void run() { + promptPackage(); + } + }; + } + + @Override + protected Control addMainControl(Composite container) { + Composite subPane = addSubPane(container); + + Text text = addText(subPane, buildTextHolder()); + + Image image = FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_CONTENT_PROPOSAL).getImage(); + GridData data = new GridData(GridData.FILL_HORIZONTAL); + data.horizontalIndent = image.getBounds().width; + text.setLayoutData(data); + + ControlContentAssistHelper.createTextContentAssistant( + text, + javaPackageCompletionProcessor + ); + + return subPane; + } + + /** + * Creates the value holder of the subject's property. + * + * @return The holder of the package name + */ + protected abstract WritablePropertyValueModel<String> buildTextHolder(); + + /** + * Prompts the user the Open Package dialog. + * + * @return Either the selected package or <code>null</code> if the user + * cancelled the dialog + */ + protected IPackageFragment choosePackage() { + + SelectionDialog selectionDialog; + + try { + selectionDialog = JavaUI.createPackageDialog( + getShell(), + getPackageFragmentRoot() + ); + } + catch (JavaModelException e) { + JptCommonUiPlugin.log(e); + return null; + } + + selectionDialog.setTitle(JptCommonUiMessages.PackageChooserPane_dialogTitle); + selectionDialog.setMessage(JptCommonUiMessages.PackageChooserPane_dialogMessage); + + IPackageFragment pack = getPackageFragment(); + + if (pack != null) { + selectionDialog.setInitialSelections(new Object[] { pack }); + } + + if (selectionDialog.open() == Window.OK) { + return (IPackageFragment) selectionDialog.getResult()[0]; + } + + return null; + } + + protected abstract IJavaProject getJavaProject(); + + /** + * Returns the package name from its subject. + * + * @return The package name or <code>null</code> if none is defined + */ + protected abstract String getPackageName(); + + /** + * The browse button was clicked, its action invokes this action which should + * prompt the user to select a package and set it. + */ + protected void promptPackage() { + IPackageFragment packageFragment = choosePackage(); + + if (packageFragment != null) { + String packageName = packageFragment.getElementName(); + this.setPackageName(packageName); + } + } + + protected abstract void setPackageName(String packageName); + + private IPackageFragment getPackageFragment() { + String packageName = getPackageName(); + + if (packageName == null) { + return null; + } + + return getPackageFragmentRoot().getPackageFragment(packageName); + } + + protected IPackageFragmentRoot getPackageFragmentRoot() { + return JDTTools.getCodeCompletionContextRoot(getJavaProject()); + } + + @Override + public void dispose() { + this.getSubjectHolder().removePropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener); + super.dispose(); + } +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/Pane.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/Pane.java new file mode 100644 index 0000000000..1bfd10fa30 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/Pane.java @@ -0,0 +1,3722 @@ +/******************************************************************************* + * Copyright (c) 2008, 2010 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import java.util.ArrayList; +import java.util.Collection; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.fieldassist.FieldDecorationRegistry; +import org.eclipse.jface.viewers.ComboViewer; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jpt.common.ui.WidgetFactory; +import org.eclipse.jpt.common.ui.internal.Tracing; +import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper; +import org.eclipse.jpt.common.ui.internal.swt.ComboModelAdapter; +import org.eclipse.jpt.common.ui.internal.swt.DateTimeModelAdapter; +import org.eclipse.jpt.common.ui.internal.swt.SpinnerModelAdapter; +import org.eclipse.jpt.common.ui.internal.swt.TriStateCheckBoxModelAdapter; +import org.eclipse.jpt.common.ui.internal.util.ControlAligner; +import org.eclipse.jpt.common.ui.internal.util.LabeledButton; +import org.eclipse.jpt.common.ui.internal.util.LabeledControlUpdater; +import org.eclipse.jpt.common.ui.internal.util.SWTUtil; +import org.eclipse.jpt.common.ui.internal.utility.swt.SWTTools; +import org.eclipse.jpt.utility.internal.NonNullBooleanTransformer; +import org.eclipse.jpt.utility.internal.StringConverter; +import org.eclipse.jpt.utility.internal.model.value.CompositeBooleanPropertyValueModel; +import org.eclipse.jpt.utility.internal.model.value.SimplePropertyValueModel; +import org.eclipse.jpt.utility.internal.model.value.TransformationPropertyValueModel; +import org.eclipse.jpt.utility.model.Model; +import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; +import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; +import org.eclipse.jpt.utility.model.value.ListValueModel; +import org.eclipse.jpt.utility.model.value.PropertyValueModel; +import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyListener; +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.events.SelectionListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.DateTime; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.List; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Spinner; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.forms.widgets.ExpandableComposite; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.Hyperlink; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.help.IWorkbenchHelpSystem; +import org.eclipse.ui.part.PageBook; + +/** + * The abstract definition of a pane which holds onto a <code>PropertyValueModel</code> + * that contains the subject of this pane. + * <p> + * It also contains convenience methods for building buttons, labels, check + * boxes, and radio buttons, etc. + * <p> + * It is possible to easily listen to any property changes coming from the + * subject, {@link #addPropertyNames(Collection)} is specify which properties + * are of interest and {@link #propertyChanged(String)} is used to notify the + * pane when the property has changed. + * + * @see FormPane + * @see DialogPane + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public abstract class Pane<T extends Model> +{ + /** + * The listener registered with the subject in order to be notified when a + * property has changed, the property names are determined by + * {@link #propertyNames()}. + */ + private PropertyChangeListener aspectChangeListener; + + /** + * The container of this composite. + */ + private final Composite container; + + /** + * The aligner responsible to align the left controls. + */ + private ControlAligner leftControlAligner; + + /** + * Flag used to stop the circular population of widgets. + */ + private boolean populating; + + /** + * The aligner responsible to align the left controls. + */ + private ControlAligner rightControlAligner; + + /** + * This listener is registered with the subject holder in order to + * automatically repopulate this pane with the new subject. + */ + private PropertyChangeListener subjectChangeListener; + + /** + * The subject of this pane. + */ + private PropertyValueModel<T> subjectHolder; + + /** + * The collection of registered sub-panes will be automatically notified + * when listeners need to be engaged or disengaged or when to populate its + * widgets. + */ + private Collection<Pane<?>> subPanes; + + /** + * The factory used to create various common widgets. + */ + private WidgetFactory widgetFactory; + + /** + * The collection of <code>Control</code>s that are displayed in this pane, + * which will have their enablement state updated when + * {@link #enableWidgets(boolean)} is called. + */ + private ArrayList<Control> managedWidgets; + + /** + * The collection of <code>Pane</code>s that are displayed in this pane, + * which will have their enablement state updated when + * {@link #enableWidgets(boolean)} is called. + */ + private ArrayList<Pane<?>> managedSubPanes; + + /** + * This enabled model is used to store the pane's base enablement state. + * If API is called to set the pane enabled, this model gets updated. If the pane is thereby + * fully enabled (controller enabled model is also in agreement) the pane's widgets are set + * enabled. + * @see #getCombinedEnabledModel() + */ + private final WritablePropertyValueModel<Boolean> baseEnabledModel + = new SimplePropertyValueModel<Boolean>(Boolean.TRUE); + + /** + * This enabled model is used to define the pane's enablement as controlled by other widgets, + * tests, etc. (for example a radio button) + * If this model is changed, and the pane is thereby fully enabled (base enabled model is also + * in agreement) the pane's widgets are set enabled. + * @see #getCombinedEnabledModel() + */ + private PropertyValueModel<Boolean> controllerEnabledModel; + + /** + * The "and" combination of {@link #baseEnabledModel} and {@link #controllerEnabledModel} + */ + private PropertyValueModel<Boolean> combinedEnabledModel; + + private PropertyChangeListener combinedEnabledModelListener; + + /** + * Creates a new <code>Pane</code>. + * + * @param parentPane The parent pane of this one + * @param parent The parent container + * + * @category Constructor + */ + protected Pane( + Pane<? extends T> parentPane, + Composite parent) { + + this(parentPane, parent, true); + } + + /** + * Creates a new <code>Pane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + * @param automaticallyAlignWidgets <code>true</code> to make the widgets + * this pane aligned with the widgets of the given parent pane; + * <code>false</code> to not align them + * + * @category Constructor + */ + protected Pane( + Pane<? extends T> parentPane, + Composite parent, + boolean automaticallyAlignWidgets) { + + this( + parentPane, + parentPane.getSubjectHolder(), + parent, + automaticallyAlignWidgets); + } + + /** + * Creates a new <code>Pane</code>. + * + * @param parentPane The parent container of this one + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + * @param automaticallyAlignWidgets <code>true</code> to make the widgets + * this pane aligned with the widgets of the given parent pane; + * <code>false</code> to not align them + * + * @category Constructor + */ + protected Pane( + Pane<? extends T> parentPane, + Composite parent, + boolean automaticallyAlignWidgets, + boolean parentManagePane) { + + this( + parentPane, + parentPane.getSubjectHolder(), + parent, + automaticallyAlignWidgets, + parentManagePane); + } + + /** + * Creates a new <code>Pane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + * + * @category Constructor + */ + protected Pane( + Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent) { + + this(parentPane, subjectHolder, parent, true); + } + + protected Pane( + Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + PropertyValueModel<Boolean> enabledModel) { + + this(parentPane, subjectHolder, parent, true, enabledModel); + } + + /** + * Creates a new <code>Pane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + * @param automaticallyAlignWidgets <code>true</code> to make the widgets + * this pane aligned with the widgets of the given parent pane; + * <code>false</code> to not align them + * + * @category Constructor + */ + protected Pane( + Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + boolean automaticallyAlignWidgets) { + + this(parentPane, subjectHolder, parent, automaticallyAlignWidgets, true); + } + + protected Pane( + Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + boolean automaticallyAlignWidgets, + PropertyValueModel<Boolean> enabledModel) { + + this(parentPane, subjectHolder, parent, automaticallyAlignWidgets, true, enabledModel); + } + + /** + * Creates a new <code>Pane</code>. + * + * @param parentPane The parent container of this one + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + * @param widgetFactory The factory used to create various widgets + * @param automaticallyAlignWidgets <code>true</code> to make the widgets + * this pane aligned with the widgets of the given parent pane; + * <code>false</code> to not align them + * @param parentManagePane <code>true</code> to have the parent pane manage + * the enabled state of this pane + * + * @category Constructor + */ + protected Pane( + Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + boolean automaticallyAlignWidgets, + boolean parentManagePane) { + + this(subjectHolder, parent, parentPane.getWidgetFactory()); + this.initialize(parentPane, automaticallyAlignWidgets, parentManagePane); + } + + protected Pane( + Pane<?> parentPane, + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + boolean automaticallyAlignWidgets, + boolean parentManagePane, + PropertyValueModel<Boolean> enabledModel) { + + this(subjectHolder, parent, parentPane.getWidgetFactory()); + this.initialize(parentPane, automaticallyAlignWidgets, parentManagePane); + this.initializeEnabledModel(enabledModel); + } + + /** + * Creates a new <code>Pane</code>. + * + * @param subjectHolder The holder of this pane's subject + * @param parent The parent container + * @param widgetFactory The factory used to create various common widgets + * + * @category Constructor + */ + protected Pane( + PropertyValueModel<? extends T> subjectHolder, + Composite parent, + WidgetFactory widgetFactory) { + + super(); + this.initialize(subjectHolder, widgetFactory); + this.container = this.addContainer(parent); + this.initializeLayout(this.container); + this.engageSubjectHolder(); + this.engageListeners(getSubject()); + this.populate(); + } + + + // ********** initialization ********** + + @SuppressWarnings("unchecked") + private void initialize( + PropertyValueModel<? extends T> subjectHolder, + WidgetFactory widgetFactory) { + + Assert.isNotNull(subjectHolder, "The subject holder cannot be null"); + + this.subjectHolder = (PropertyValueModel<T>) subjectHolder; + this.widgetFactory = widgetFactory; + this.subPanes = new ArrayList<Pane<?>>(); + this.managedWidgets = new ArrayList<Control>(); + this.managedSubPanes = new ArrayList<Pane<?>>(); + this.leftControlAligner = new ControlAligner(); + this.rightControlAligner = new ControlAligner(); + this.subjectChangeListener = this.buildSubjectChangeListener(); + this.aspectChangeListener = this.buildAspectChangeListener(); + + this.initialize(); + } + + protected void initialize() { + // do nothing by default + } + + /** + * Registers this pane with the parent pane. + * + * @param parentPane The parent pane + * @param automaticallyAlignWidgets <code>true</code> to make the widgets + * this pane aligned with the widgets of the given parent pane; + * <code>false</code> to not align them + * @param parentManagePane <code>true</code> to have the parent pane manage + * the enabled state of this pane + * + * @category Initialization + */ + private void initialize( + Pane<?> parentPane, + boolean automaticallyAlignWidgets, + boolean parentManagePane) { + + // Register this pane with the parent pane, it will call the methods + // automatically (engageListeners(), disengageListeners(), populate(), + // dispose(), etc) + parentPane.registerSubPane(this); + + if (parentManagePane) { + parentPane.manageSubPane(this); + } + + // Align the left and right controls with the controls from the parent + // pane + if (automaticallyAlignWidgets) { + parentPane.addAlignLeft(this); + parentPane.addAlignRight(this); + } + } + + private void initializeEnabledModel(PropertyValueModel<Boolean> enabledModel) { + this.controllerEnabledModel = enabledModel; + this.combinedEnabledModel = + CompositeBooleanPropertyValueModel.and(this.baseEnabledModel, this.controllerEnabledModel); + this.combinedEnabledModelListener = buildCombinedEnabledModelListener(); + this.combinedEnabledModel.addPropertyChangeListener( + PropertyValueModel.VALUE, this.combinedEnabledModelListener); + enableWidgets_(getCombinedEnablement()); + } + + private PropertyChangeListener buildCombinedEnabledModelListener() { + return new SWTPropertyChangeListenerWrapper(buildControllerEnabledModelListener_()); + } + + private PropertyChangeListener buildControllerEnabledModelListener_() { + return new PropertyChangeListener() { + @SuppressWarnings("unchecked") + public void propertyChanged(PropertyChangeEvent e) { + Pane.this.controllerEnablementChanged(); + } + }; + } + + /** + * Initializes the layout of this pane. + * + * @param container The parent container + * + * @category Layout + */ + protected abstract void initializeLayout(Composite container); + + private void manageWidget(Control control) { + if (this.managedWidgets.contains(control)) { + throw new IllegalStateException(); + } + this.managedWidgets.add(control); + } + + private void manageSubPane(Pane<?> subPane) { + if (this.managedSubPanes.contains(subPane)) { + throw new IllegalStateException(); + } + this.managedSubPanes.add(subPane); + } + + /** + * Adds the given pane's widgets (those that were registered with its + * left <code>ControlAligner</code>) to this pane's left + * <code>ControlAligner</code> so that their width can be adjusted to have + * the width of the widest widget. + * + * @param pane The pane containing the widgets to add + * + * @category Layout + */ + protected final void addAlignLeft(Pane<?> container) { + this.leftControlAligner.add(container.leftControlAligner); + } + + /** + * Adds the given control to the collection of widgets that have their width + * adjust with the width of the widest widget. The left alignment is usually + * used for labels. + * + * @param pane The pane to add + * + * @category Layout + */ + protected final void addAlignLeft(Control control) { + this.leftControlAligner.add(control); + } + + /** + * Adds the given pane's widgets (those that were registered with its + * right <code>ControlAligner</code>) to this pane's right + * <code>ControlAligner</code> so that their width can be adjusted to have + * the width of the widest widget. + * + * @param pane The pane containing the widgets to add + * + * @category Layout + */ + protected final void addAlignRight(Pane<?> container) { + this.rightControlAligner.add(container.rightControlAligner); + } + + /** + * Adds the given control to the collection of widgets that have their width + * adjust with the width of the widest widget. The left alignment is usually + * used for buttons. + * + * @param pane The pane to add + * + * @category Layout + */ + protected final void addAlignRight(Control control) { + this.rightControlAligner.add(control); + } + + /** + * Adds the given pane's controls (those that were registered for + * alignment) from this pane. + * + * @param pane The pane containing the widgets to add for + * alignment + * + * @category Layout + */ + protected final void addPaneForAlignment(Pane<?> container) { + addAlignLeft(container); + addAlignRight(container); + } + + /** + * Adds any property names to the given collection in order to be notified + * when the actual property changes in the subject. + * + * @param propertyNames The collection of property names to register with the + * subject + */ + protected void addPropertyNames(Collection<String> propertyNames) { + } + + private PropertyChangeListener buildAspectChangeListener() { + return new SWTPropertyChangeListenerWrapper(buildAspectChangeListener_()); + } + + private PropertyChangeListener buildAspectChangeListener_() { + return new PropertyChangeListener() { + public void propertyChanged(PropertyChangeEvent e) { + //subject() could have changed or is null because of the possibility of + //"jumping" on the UI thread here and a selection change occuring + if (e.getSource() == getSubject()) { + updatePane(e.getPropertyName()); + } + } + }; + } + + /** + * Creates a new button using the given information. + * + * @param parent The parent container + * @param buttonText The button's text + * @param buttonAction The action to be invoked when the button is pressed + * @return The newly created <code>Button</code> + * + * @category Layout + */ + protected final Button addButton(Composite container, + String text, + final Runnable buttonAction) { + + return this.addButton(container, text, null, buttonAction); + } + + /** + * Creates a new unmanaged <code>Button</code> widget. Unmanaged means + * that this Pane will not handle the enabling/disabling of this widget. + * The owning object will handle it with its own PaneEnabler or ControlEnabler. + * + * @param parent The parent container + * @param buttonText The button's text + * @param buttonAction The action to be invoked when the button is pressed + * @return The newly created <code>Button</code> + * + * @category Layout + */ + protected final Button addUnmanagedButton(Composite container, + String text, + final Runnable buttonAction) { + + return this.addUnmanagedButton(container, text, null, buttonAction); + } + + /** + * Creates a new button using the given information. + * + * @param parent The parent container + * @param buttonText The button's text + * @param helpId The topic help ID to be registered for the new check box + * @param buttonAction The action to be invoked when the button is pressed + * @return The newly created <code>Button</code> + * + * @category Layout + */ + protected final Button addButton(Composite container, + String text, + String helpId, + final Runnable buttonAction) { + + Button button = addUnmanagedButton(container, text, helpId, buttonAction); + this.manageWidget(button); + + return button; + } + + /** + * Creates a new unmanaged <code>Button</code> widget. Unmanaged means + * that this Pane will not handle the enabling/disabling of this widget. + * The owning object will handle it with its own PaneEnabler or ControlEnabler. + * + * @param parent The parent container + * @param buttonText The button's text + * @param helpId The topic help ID to be registered for the new check box + * @param buttonAction The action to be invoked when the button is pressed + * @return The newly created <code>Button</code> + * + * @category Layout + */ + private Button addUnmanagedButton(Composite container, + String text, + String helpId, + final Runnable buttonAction) { + + Button button = this.widgetFactory.createButton(container, text); + button.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + SWTUtil.asyncExec(buttonAction); + } + }); + + if (helpId != null) { + getHelpSystem().setHelp(button, helpId); + } + + GridData gridData = new GridData(); + gridData.grabExcessHorizontalSpace = false; + gridData.horizontalAlignment = GridData.FILL; + button.setLayoutData(gridData); + + return button; + } + + /** + * This layout will leave space for decorations on widgets. + * Whether decorated or not, all of the widgets need the same indent + * so that they align properly. + */ + protected GridData getFieldGridData() { + int margin = FieldDecorationRegistry.getDefault() + .getMaximumDecorationWidth(); + GridData data = new GridData(); + data.horizontalAlignment = SWT.FILL; + data.widthHint = IDialogConstants.ENTRY_FIELD_WIDTH + margin; + data.horizontalIndent = margin; + data.grabExcessHorizontalSpace = true; + return data; + } + + /** + * Creates a new check box using the given information. + * + * @param parent The parent container + * @param buttonText The button's text + * @param booleanHolder The holder of the selection state + * @param helpId The topic help ID to be registered for the new check box + * @return The newly created <code>Button</code> + * + * @category Layout + */ + protected final Button addCheckBox( + Composite parent, + String buttonText, + WritablePropertyValueModel<Boolean> booleanHolder, + String helpId) { + + return this.addToggleButton( + parent, + buttonText, + booleanHolder, + helpId, + SWT.CHECK); + } + + protected final Button addCheckBox( + Composite parent, + String buttonText, + WritablePropertyValueModel<Boolean> booleanHolder, + String helpId, + PropertyValueModel<Boolean> enabledModel) { + + Button button = this.addUnmanagedToggleButton(parent, buttonText, booleanHolder, helpId, SWT.CHECK); + this.controlEnabledState(enabledModel, button); + return button; + } + + /** + * Creates a new <code>Section</code> that can be collapsed. A sub-pane is + * automatically added as its client and is the returned <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + protected final Composite addCollapsibleSection( + Composite container, + String sectionText) { + + return this.addCollapsibleSection( + container, + sectionText, + new SimplePropertyValueModel<Boolean>(Boolean.FALSE)); + } + + /** + * Creates a new <code>Section</code> that can be collapsed. A sub-pane is + * automatically added as its client and is the returned <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @param description The section's description or <code>null</code> if none + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + protected final Composite addCollapsibleSection(Composite container, + String sectionText, + String description) { + + return this.addCollapsibleSection( + container, + sectionText, + description, + new SimplePropertyValueModel<Boolean>(Boolean.FALSE) + ); + } + + /** + * Creates a new <code>Section</code>. A sub-pane is automatically added as + * its client and is the returned <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @param type The type of section to create + * @param expandedStateHolder The holder of the boolean that will dictate + * when to expand or collapse the section + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + private Composite addCollapsibleSection(Composite container, + String sectionText, + int type, + PropertyValueModel<Boolean> expandedStateHolder) { + + return addCollapsibleSection(container, sectionText, null, type, expandedStateHolder); + } + + /** + * Creates a new <code>Section</code>. A sub-pane is automatically added as + * its client and is the returned <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @param description The section's description or <code>null</code> if none + * was provided + * @param type The type of section to create + * @param expandedStateHolder The holder of the boolean that will dictate + * when to expand or collapse the section + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + private Composite addCollapsibleSection(Composite container, + String sectionText, + String description, + int type, + PropertyValueModel<Boolean> expandedStateHolder) { + + Composite subPane = this.addSection( + container, + sectionText, + description, + ExpandableComposite.TWISTIE | type + ); + + Section section = (Section) subPane.getParent(); + + expandedStateHolder.addPropertyChangeListener( + PropertyValueModel.VALUE, + buildExpandedStateChangeListener(section) + ); + + section.setExpanded( + expandedStateHolder.getValue() != null ? expandedStateHolder.getValue() : true + ); + + return subPane; + } + + /** + * Creates a new <code>Section</code>. A sub-pane is automatically added as + * its client and is the returned <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @param expandedStateHolder The holder of the boolean that will dictate + * when to expand or collapse the section + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + protected final Composite addCollapsibleSection(Composite container, + String sectionText, + PropertyValueModel<Boolean> expandedStateHolder) { + + return this.addCollapsibleSection( + container, + sectionText, + ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE, + expandedStateHolder + ); + } + + /** + * Creates a new <code>Section</code>. A sub-pane is automatically added as + * its client and is the returned <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @param description The section's description or <code>null</code> if none + * @param expandedStateHolder The holder of the boolean that will dictate + * when to expand or collapse the section + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + protected final Composite addCollapsibleSection(Composite container, + String sectionText, + String description, + PropertyValueModel<Boolean> expandedStateHolder) { + + return this.addCollapsibleSection( + container, + sectionText, + description, + ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE, + expandedStateHolder + ); + } + + /** + * Creates a new <code>Section</code>. A sub-pane is automatically added as + * its client which can be typed cast directly as a <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @param expandedStateHolder The holder of the boolean that will dictate + * when to expand or collapse the section + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + protected final Composite addCollapsibleSubSection(Composite container, + String sectionText, + PropertyValueModel<Boolean> expandedStateHolder) { + + return this.addCollapsibleSection( + container, + sectionText, + SWT.NULL, + expandedStateHolder + ); + } + + /** + * Creates a new non-editable <code>Combo</code>. + * + * @param container The parent container + * @return The newly created <code>Combo</code> + * + * @category Layout + */ + protected final Combo addCombo(Composite container) { + Combo combo = this.addUnmanagedCombo(container); + this.manageWidget(combo); + return combo; + } + + /** + * Creates a new non-editable <code>Combo</code>. + * + * @param container The parent container + * @return The newly created <code>Combo</code> + * + * @category Layout + */ + private Combo addUnmanagedCombo(Composite container) { + Combo combo = this.widgetFactory.createCombo(container); + combo.setLayoutData(getFieldGridData()); + return combo; + } + + /** + * Creates a new non-editable <code>Combo</code>. + * + * @param container The parent container + * @param listHolder The <code>ListValueHolder</code> + * @param selectedItemHolder The holder of the selected item + * @param stringConverter The converter responsible to transform each item + * into a string representation + * @return The newly created <code>Combo</code> + * + * @category Layout + */ + protected final <V> Combo addCombo(Composite container, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + StringConverter<V> stringConverter) { + + Combo combo = this.addCombo(container); + + ComboModelAdapter.adapt( + listHolder, + selectedItemHolder, + combo, + stringConverter + ); + + return combo; + } + + /** + * Creates a new non-editable <code>Combo</code>. + * + * @param container The parent container + * @param listHolder The <code>ListValueHolder</code> + * @param selectedItemHolder The holder of the selected item + * @param stringConverter The converter responsible to transform each item + * into a string representation + * @return The newly created <code>Combo</code> + * + * @category Layout + */ + private <V> Combo addUnmanagedCombo(Composite container, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + StringConverter<V> stringConverter) { + + Combo combo = this.addUnmanagedCombo(container); + + ComboModelAdapter.adapt( + listHolder, + selectedItemHolder, + combo, + stringConverter + ); + + return combo; + } + + protected final <V> Combo addCombo( + Composite container, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + StringConverter<V> stringConverter, + PropertyValueModel<Boolean> enabledModel) { + + Combo combo = this.addUnmanagedCombo(container, listHolder, selectedItemHolder, stringConverter); + this.controlEnabledState(enabledModel, combo); + return combo; + } + + /** + * Creates a new <code>ComboViewer</code> using a <code>Combo</code>. + * + * @param container The parent container + * @param labelProvider The provider responsible to convert the combo's items + * into human readable strings + * @return The newly created <code>ComboViewer</code> + * + * @category Layout + */ + protected final ComboViewer addComboViewer(Composite container, + IBaseLabelProvider labelProvider) { + + Combo combo = this.addCombo(container); + ComboViewer viewer = new ComboViewer(combo); + viewer.setLabelProvider(labelProvider); + return viewer; + } + + /** + * Creates the main container of this pane. The layout and layout data are + * automatically set. + * + * @param parent The parent container + * @return The newly created <code>Composite</code> that will holds all the + * widgets created by this pane through {@link #initializeLayout(Composite)} + * + * @category Layout + */ + protected Composite addContainer(Composite parent) { + return this.addSubPane(parent); + } + + protected final <V> Combo addEditableCombo( + Composite container, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + StringConverter<V> stringConverter, + PropertyValueModel<Boolean> enabledModel) { + + Combo combo = this.addUnmanagedEditableCombo(container, listHolder, selectedItemHolder, stringConverter); + this.controlEnabledState(enabledModel, combo); + return combo; + } + + protected final Combo addEditableCombo( + Composite container) { + + Combo combo = this.widgetFactory.createEditableCombo(container); + combo.setLayoutData(getFieldGridData()); + this.manageWidget(combo); + return combo; + } + + /** + * Creates a new editable <code>Combo</code>. + * + * @param container The parent container + * @param listHolder The <code>ListValueHolder</code> + * @param selectedItemHolder The holder of the selected item + * @param stringConverter The converter responsible to transform each item + * into a string representation + * @return The newly created <code>Combo</code> + * + * @category Layout + */ + protected final <V> Combo addEditableCombo(Composite container, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + StringConverter<V> stringConverter) { + + Combo combo = this.addEditableCombo(container); + + ComboModelAdapter.adapt( + listHolder, + selectedItemHolder, + combo, + stringConverter + ); + + return combo; + } + + /** + * Creates a new editable <code>ComboViewer</code> using a <code>Combo</code>. + * + * @param container The parent container + * @param labelProvider The provider responsible to convert the combo's items + * into human readable strings + * @return The newly created <code>ComboViewer</code> + * + * @category Layout + */ + protected final ComboViewer addEditableComboViewer(Composite container, + IBaseLabelProvider labelProvider) { + + Combo combo = this.addEditableCombo(container); + ComboViewer viewer = new ComboViewer(combo); + viewer.setLabelProvider(labelProvider); + return viewer; + } + + private PropertyChangeListener buildExpandedStateChangeListener(final Section section) { + return new SWTPropertyChangeListenerWrapper(buildExpandedStateChangeListener_(section)); + } + + private PropertyChangeListener buildExpandedStateChangeListener_(final Section section) { + return new PropertyChangeListener() { + public void propertyChanged(final PropertyChangeEvent e) { + Boolean value = (Boolean) e.getNewValue(); + if (value == null) { + value = Boolean.TRUE; + } + section.setExpanded(value); + } + }; + } + + /** + * Creates a new <code>Hyperlink</code> that will invoked the given + * <code>Runnable</code> when selected. The given action is always invoked + * from the UI thread. + * + * @param parent The parent container + * @param text The hyperlink's text + * @param hyperLinkAction The action to be invoked when the link was selected + * return The newly created <code>Hyperlink</code> + * + * @category Layout + */ + protected final Hyperlink addHyperlink(Composite parent, + String text, + final Runnable hyperLinkAction) { + + Hyperlink link = this.widgetFactory.createHyperlink(parent, text); + this.manageWidget(link); + + link.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + + Hyperlink hyperLink = (Hyperlink) e.widget; + + if (hyperLink.isEnabled()) { + SWTUtil.asyncExec(hyperLinkAction); + } + } + }); + + return link; + } + + /** + * Creates a new label using the given information. + * + * @param parent The parent container + * @param labelText The label's text + * + * @category Layout + */ + protected final Label addLabel(Composite container, + String labelText) { + + Label label = addUnmanagedLabel(container, labelText); + manageWidget(label); + return label; + } + + protected final Label addLabel( + Composite container, + String labelText, + PropertyValueModel<Boolean> enabledModel + ) { + Label label = this.addUnmanagedLabel(container, labelText); + this.controlEnabledState(enabledModel, label); + return label; + } + + /** + * Creates a new unmanaged <code>Label</code> widget. Unmanaged means + * that this Pane will not handle the enabling/disabling of this widget. + * The owning object will handle it with its own PaneEnabler or ControlEnabler. + * + * @param parent The parent container + * @param labelText The label's text + * + * @category Layout + */ + private Label addUnmanagedLabel(Composite container, + String labelText) { + + return this.widgetFactory.createLabel(container, labelText); + } + + /** + * Creates a new container that will have a non-editable combo labeled with + * the given text. + * + * @param container The parent container + * @param labelText The text of the label + * @param listHolder The <code>ListValueHolder</code> + * @param selectedItemHolder The holder of the selected item + * @param rightControl The control shown to the right of the main widget + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The container of the label and the given center control + * + * @category Layout + */ + protected final <V> Combo addLabeledCombo(Composite container, + String labelText, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + StringConverter<V> stringConverter, + Control rightControl, + String helpId) { + + Combo combo = this.addCombo( + container, + listHolder, + selectedItemHolder, + stringConverter + ); + + this.addLabeledComposite( + container, + labelText, + (combo.getParent() != container) ? combo.getParent() : combo, + rightControl, + helpId + ); + + return combo; + } + + /** + * Creates a new container that will have a non-editable combo labeled with + * the given text. + * + * @param container The parent container + * @param labelText The text of the label + * @param listHolder The <code>ListValueHolder</code> + * @param selectedItemHolder The holder of the selected item + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The container of the label and the given center control + * + * @category Layout + */ + protected final <V> Combo addLabeledCombo(Composite container, + String labelText, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + StringConverter<V> stringConverter, + String helpId) { + + return this.addLabeledCombo( + container, + labelText, + listHolder, + selectedItemHolder, + stringConverter, + null, + helpId + ); + } + + /** + * Creates a new container that will have the given center control labeled + * with the given label. + * + * @param container The parent container + * @param leftControl The widget shown to the left of the main widget + * @param centerControl The main widget + * @param rightControl The control shown to the right of the main widget + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The container of the label and the given center control + * + * @category Layout + */ + protected final Composite addLabeledComposite(Composite container, + Control leftControl, + Control centerControl, + Control rightControl, + String helpId) { + + // Container for the label and main composite + container = this.addSubPane(container, 3, 0, 0, 0, 0); + + // Left control + GridData gridData = new GridData(); + gridData.horizontalAlignment = GridData.BEGINNING; + gridData.grabExcessHorizontalSpace = false; + leftControl.setLayoutData(gridData); + + // Re-parent the left control to the new sub pane + leftControl.setParent(container); + this.addAlignLeft(leftControl); + + + // Re-parent the center control to the new sub pane + centerControl.setParent(container); + + // Register the help id for the center control + if (helpId != null) { + getHelpSystem().setHelp(centerControl, helpId); + } + + // Right control + if (rightControl == null) { + Composite spacer = this.addPane(container); + spacer.setLayout(this.buildSpacerLayout()); + rightControl = spacer; + } + else { + rightControl.setParent(container); + + // Register the help id for the right control + if (helpId != null) { + getHelpSystem().setHelp(rightControl, helpId); + } + } + + gridData = new GridData(); + gridData.horizontalAlignment = GridData.FILL_HORIZONTAL; + gridData.grabExcessHorizontalSpace = false; + + rightControl.setLayoutData(gridData); + this.addAlignRight(rightControl); + + return container; + } + + /** + * Creates a new container that will have the given center control labeled + * with the given label. + * + * @param container The parent container + * @param label The label used to describe the center control + * @param centerControl The main widget + * @param helpId The topic help ID to be registered for the given center + * control + * @return The container of the label and the given center control + * + * @category Layout + */ + protected final Composite addLabeledComposite(Composite container, + Control label, + Control centerControl, + String helpId) { + + return this.addLabeledComposite( + container, + label, + centerControl, + null, + helpId + ); + } + + /** + * Creates a new container that will have the given center composite labeled + * with the given label text. + * + * @param container The parent container + * @param labelText The text to label the main composite + * @param centerPane The main widget + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The container of the label and the given center control + * + * @category Layout + */ + protected final Composite addLabeledComposite(Composite container, + String labelText, + Pane<?> centerPane, + String helpId) { + + return this.addLabeledComposite( + container, + labelText, + centerPane.getControl(), + helpId + ); + } + + /** + * Creates a new container that will have the given center composite labeled + * with the given label text. + * + * @param container The parent container + * @param labelText The text to label the main composite + * @param centerControl The main widget + * @return The container of the label and the given center control + * + * @category Layout + */ + protected final Composite addLabeledComposite(Composite container, + String labelText, + Control centerControl) { + + + return this.addLabeledComposite( + container, + labelText, + centerControl, + null, + null + ); + } + + /** + * Creates a new container that will have the given center composite labeled + * with the given label text. + * + * @param container The parent container + * @param labelText The text to label the main composite + * @param centerControl The main widget + * @param rightControl The control shown to the right of the main widget + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The container of the label and the given center control + * + * @category Layout + */ + protected final Composite addLabeledComposite(Composite container, + String labelText, + Control centerControl, + Control rightControl, + String helpId) { + + return this.addLabeledComposite( + container, + this.addLabel(container, labelText), + centerControl, + rightControl, + helpId + ); + } + + /** + * Creates a new container that will have the given center composite labeled + * with the given label text. + * + * @param container The parent container + * @param labelText The text to label the main composite + * @param centerControl The main widget + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The container of the label and the given center control + * + * @category Layout + */ + protected final Composite addLabeledComposite(Composite container, + String labelText, + Control centerControl, + String helpId) { + + Label label = this.addLabel(container, labelText); + + return this.addLabeledComposite( + container, + label, + centerControl, + helpId + ); + } + + /** + * Creates a new container that will have the given center control labeled + * with the given label. + * + * @param container The parent container + * @param leftControl The widget shown to the left of the main widget + * @param centerControl The main widget + * @param rightControl The control shown to the right of the main widget + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The newly created <code>CCombo</code> + * + * @category Layout + */ + protected final Combo addLabeledEditableCombo(Composite container, + String labelText, + ModifyListener comboListener, + Control rightControl, + String helpId) { + + Combo combo = this.addEditableCombo(container); + combo.addModifyListener(comboListener); + + this.addLabeledComposite( + container, + labelText, + (combo.getParent() != container) ? combo.getParent() : combo, + rightControl, + helpId + ); + + return combo; + } + + /** + * Creates a new container that will have an editable combo labeled with the + * given text. + * + * @param container The parent container + * @param labelText The text of the label + * @param comboListener The listener that will be notified when the selection + * changes + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The newly created <code>CCombo</code> + * + * @category Layout + */ + protected final Combo addLabeledEditableCombo(Composite container, + String labelText, + ModifyListener comboListener, + String helpId) { + + return this.addLabeledEditableCombo( + container, + labelText, + comboListener, + null, + helpId + ); + } + + /** + * Creates a new container that will have the given center control labeled + * with the given label. + * + * @param container The parent container + * @param leftControl The widget shown to the left of the main widget + * @param centerControl The main widget + * @param labelProvider The provider responsible to convert the combo's items + * into human readable strings + * @param rightControl The control shown to the right of the main widget + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The newly created <code>CCombo</code> + * + * @category Layout + */ + protected final Combo addLabeledEditableComboViewer(Composite container, + String labelText, + ModifyListener comboListener, + ILabelProvider labelProvider, + Control rightControl, + String helpId) { + + ComboViewer comboViewer = this.addEditableComboViewer( + container, + labelProvider + ); + + Combo combo = comboViewer.getCombo(); + combo.addModifyListener(comboListener); + + this.addLabeledComposite( + container, + labelText, + (combo.getParent() != container) ? combo.getParent() : combo, + rightControl, + helpId + ); + + return combo; + } + + /** + * Creates a new container that will have an editable combo labeled with the + * given text. + * + * @param container The parent container + * @param labelText The text of the label + * @param comboListener The listener that will be notified when the selection + * changes + * @param labelProvider The provider responsible to convert the combo's items + * into human readable strings + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The newly created <code>CCombo</code> + * + * @category Layout + */ + protected final Combo addLabeledEditableComboViewer(Composite container, + String labelText, + ModifyListener comboListener, + ILabelProvider labelProvider, + String helpId) { + + return this.addLabeledEditableComboViewer( + container, + labelText, + comboListener, + labelProvider, + null, + helpId + ); + } + + /** + * Creates a new container that will have an editable combo labeled with the + * given text. + * + * @param container The parent container + * @param labelText The text of the label + * @param listHolder The <code>ListValueHolder</code> + * @param selectedItemHolder The holder of the selected item + * @param helpId The topic help ID to be registered for the given center + * composite + * @return The newly created <code>CCombo</code> + * + * @category Layout + */ + protected final <V> Combo addLabeledEditableCombo(Composite container, + String labelText, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + String helpId) { + + return this.addLabeledEditableCombo( + container, + labelText, + listHolder, + selectedItemHolder, + StringConverter.Default.<V>instance(), + null, + helpId + ); + } + + /** + * Creates a new container that will have the given center control labeled + * with the given label. + * + * @param container The parent container + * @param labelText The text of the label + * @param listHolder The <code>ListValueHolder</code> + * @param selectedItemHolder The holder of the selected item + * @param stringConverter The converter responsible to transform each item + * into a string representation + * @param rightControl The control shown to the right of the main widget + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The newly created <code>Combo</code> + * + * @category Layout + */ + protected final <V> Combo addLabeledEditableCombo(Composite container, + String labelText, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + StringConverter<V> stringConverter, + Control rightControl, + String helpId) { + + Combo combo = this.addEditableCombo( + container, + listHolder, + selectedItemHolder, + stringConverter + ); + + this.addLabeledComposite( + container, + labelText, + (combo.getParent() != container) ? combo.getParent() : combo, + rightControl, + helpId + ); + + return combo; + } + + /** + * Creates a new container that will have an editable combo labeled with the + * given text. + * + * @param container The parent container + * @param labelText The text of the label + * @param listHolder The <code>ListValueHolder</code> + * @param selectedItemHolder The holder of the selected item + * @param stringConverter The converter responsible to transform each item + * into a string representation + * @param helpId The topic help ID to be registered for the given center + * compositer + * @return The newly created <code>Combo</code> + * + * @category Layout + */ + protected final <V> Combo addLabeledEditableCombo(Composite container, + String labelText, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + StringConverter<V> stringConverter, + String helpId) { + + return this.addLabeledEditableCombo( + container, + labelText, + listHolder, + selectedItemHolder, + stringConverter, + null, + helpId + ); + } + + /** + * Creates a new container that will have a text field as the center control + * labeled with the given label. + * + * @param container The parent container + * @param labelText The text area's label + * @param textHolder The holder of the text field's input + * @param lineCount The number of lines the text area should display + * @param helpId The topic help ID to be registered for the text field + * @return The newly created <code>Text</code> + * + * @category Layout + */ + protected final Text addLabeledMultiLineText(Composite container, + String labelText, + WritablePropertyValueModel<String> textHolder, + int lineCount, + String helpId) { + + Text text = this.addMultiLineText(container, textHolder, lineCount); + + container = this.addLabeledComposite( + container, + labelText, + text, + helpId + ); + + int textHeight = text.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; + + // Specify the number of lines the text area should display + GridData gridData = (GridData) text.getLayoutData(); + gridData.heightHint = text.getLineHeight() * lineCount; + + // Move the label to the top of its cell + Control label = container.getChildren()[0]; + int labelHeight = label.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; + + gridData = (GridData) label.getLayoutData(); + gridData.verticalAlignment = SWT.TOP; + gridData.verticalIndent += (Math.abs(textHeight - labelHeight) / 2); + + return text; + } + + /** + * Creates a new container that will have a text field as the center control + * labeled with the given label. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @return The newly created <code>Text</code> + * + * @category Layout + */ + protected final Text addLabeledPasswordText(Composite container, + String labelText, + WritablePropertyValueModel<String> textHolder) { + + return this.addLabeledPasswordText( + container, + labelText, + textHolder, + null + ); + } + + /** + * Creates a new container that will have a text field as the center control + * labeled with the given label. + * + * @param container The parent container + * @param labelText The text field's label + * @param rightComponent The component to be placed to the right of the text + * field + * @param textHolder The holder of the text field's input + * @param helpId The topic help ID to be registered for the text field + * @return The newly created <code>Text</code> + * + * @category Layout + */ + protected final Text addLabeledPasswordText(Composite container, + String labelText, + WritablePropertyValueModel<String> textHolder, + Control rightControl, + String helpId) { + + Text text = this.addPasswordText(container, textHolder); + + this.addLabeledComposite( + container, + labelText, + text, + rightControl, + helpId + ); + + return text; + } + + /** + * Creates a new container that will have a text field as the center control + * labeled with the given label. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @param helpId The topic help ID to be registered for the text field + * @return The newly created <code>Text</code> + * + * @category Layout + */ + protected final Text addLabeledPasswordText(Composite container, + String labelText, + WritablePropertyValueModel<String> textHolder, + String helpId) { + + return this.addLabeledPasswordText( + container, + labelText, + textHolder, + null, + helpId + ); + } + + /** + * Creates a new spinner. + * + * @param parent The parent container + * @param labelText The label's text + * @param numberHolder The holder of the integer value + * @param defaultValue The value shown when the holder has <code>null</code> + * @param minimumValue The minimum value that the spinner will allow + * @param maximumValue The maximum value that the spinner will allow + * @param rightControl The widget to be placed to the right of spinner + * @param helpId The topic help ID to be registered for the spinner + * @return The newly created <code>Spinner</code> + * + * @category Layout + */ + protected final Spinner addLabeledSpinner(Composite parent, + String labelText, + WritablePropertyValueModel<Integer> numberHolder, + int defaultValue, + int minimumValue, + int maximumValue, + Control rightControl, + String helpId) { + + Spinner spinner = this.addSpinner( + parent, + numberHolder, + defaultValue, + minimumValue, + maximumValue, + helpId + ); + Label label = addLabel(parent, labelText); + addLabeledComposite( + parent, + label, + (spinner.getParent() != parent) ? spinner.getParent() : spinner, + rightControl, + helpId + ); + + GridData gridData = (GridData) spinner.getLayoutData(); + gridData.horizontalAlignment = GridData.BEGINNING; + + return spinner; + } + + /** + * Creates a new managed spinner. Managed means that this Pane will + * handle enabling/disabling of this widget if a PaneEnabler is used. + * + * @param parent The parent container + * @param numberHolder The holder of the integer value + * @param defaultValue The value shown when the holder has <code>null</code> + * @param minimumValue The minimum value that the spinner will allow + * @param maximumValue The maximum value that the spinner will allow + * @param helpId The topic help ID to be registered for the new button + * @return The newly created <code>Spinner</code> + * + * @category Layout + */ + protected final Spinner addSpinner(Composite parent, + WritablePropertyValueModel<Integer> numberHolder, + int defaultValue, + int minimumValue, + int maximumValue, + String helpId) { + + Spinner spinner = addUnmanagedSpinner(parent, numberHolder, defaultValue, minimumValue, maximumValue, helpId); + this.manageWidget(spinner); + return spinner; + } + + /** + * Creates a new unmanaged spinner. Unmanaged means that this Pane will + * not handle the enabling/disabling of this widget. The owning object will handle + * it with its own PaneEnabler or ControlEnabler. + * + * @param parent The parent container + * @param numberHolder The holder of the integer value + * @param defaultValue The value shown when the holder has <code>null</code> + * @param minimumValue The minimum value that the spinner will allow + * @param maximumValue The maximum value that the spinner will allow + * @param helpId The topic help ID to be registered for the new button + * @return The newly created <code>Spinner</code> + * + * @category Layout + */ + private Spinner addUnmanagedSpinner(Composite parent, + WritablePropertyValueModel<Integer> numberHolder, + int defaultValue, + int minimumValue, + int maximumValue, + String helpId) { + + Spinner spinner = this.widgetFactory.createSpinner(parent); + spinner.setMinimum(minimumValue); + spinner.setMaximum(maximumValue); + GridData gridData = getFieldGridData(); + gridData.grabExcessHorizontalSpace = false; + spinner.setLayoutData(gridData); + + SpinnerModelAdapter.adapt(numberHolder, spinner, defaultValue); + + if (helpId != null) { + getHelpSystem().setHelp(spinner, helpId); + } + + return spinner; + } + + /** + * Creates a new managed DateTime of type SWT.TIME. Managed means that this Pane will + * handle enabling/disabling of this widget if a PaneEnabler is used. + * + * @param parent The parent container + * @param hoursHolder The holder of the hours integer value + * @param minutesHolder The holder of the minutes integer value + * @param secondsHolder The holder of the seconds integer value + * @param helpId The topic help ID to be registered for the new dateTime + * @return The newly created <code>DateTime</code> + * + * @category Layout + */ + protected final DateTime addDateTime(Composite parent, + WritablePropertyValueModel<Integer> hoursHolder, + WritablePropertyValueModel<Integer> minutesHolder, + WritablePropertyValueModel<Integer> secondsHolder, + String helpId) { + + DateTime dateTime = this.addUnmanagedDateTime(parent, hoursHolder, minutesHolder, secondsHolder, helpId); + this.manageWidget(dateTime); + + return dateTime; + } + + protected final DateTime addDateTime( + Composite parent, + WritablePropertyValueModel<Integer> hoursHolder, + WritablePropertyValueModel<Integer> minutesHolder, + WritablePropertyValueModel<Integer> secondsHolder, + String helpId, + PropertyValueModel<Boolean> enabledModel + ) { + DateTime dateTime = this.addUnmanagedDateTime(parent, hoursHolder, minutesHolder, secondsHolder, helpId); + this.controlEnabledState(enabledModel, dateTime); + return dateTime; + } + + /** + * Creates a new unmanaged DateTime of type SWT.TIME. Unmanaged means that this Pane will + * not handle the enabling/disabling of this widget. The owning object will handle + * it with its own PaneEnabler or ControlEnabler. + * + * @param parent The parent container + * @param hoursHolder The holder of the hours integer value + * @param minutesHolder The holder of the minutes integer value + * @param secondsHolder The holder of the seconds integer value + * @param helpId The topic help ID to be registered for the new dateTime + * @return The newly created <code>DateTime</code> + * + * @category Layout + */ + private DateTime addUnmanagedDateTime(Composite parent, + WritablePropertyValueModel<Integer> hoursHolder, + WritablePropertyValueModel<Integer> minutesHolder, + WritablePropertyValueModel<Integer> secondsHolder, + String helpId) { + + DateTime dateTime = this.widgetFactory.createDateTime(parent, SWT.TIME); + + DateTimeModelAdapter.adapt(hoursHolder, minutesHolder, secondsHolder, dateTime); + + if (helpId != null) { + getHelpSystem().setHelp(dateTime, helpId); + } + + return dateTime; + } + /** + * Creates a new editable <code>Combo</code>. + * + * @param container The parent container + * @param listHolder The <code>ListValueHolder</code> + * @param selectedItemHolder The holder of the selected item + * @param stringConverter The converter responsible to transform each item + * into a string representation + * @return The newly created <code>CCombo</code> + * + * @category Layout + */ + private <V> Combo addUnmanagedEditableCombo(Composite container, + ListValueModel<V> listHolder, + WritablePropertyValueModel<V> selectedItemHolder, + StringConverter<V> stringConverter) { + + Combo combo = addUnmanagedEditableCombo(container); + + ComboModelAdapter.adapt( + listHolder, + selectedItemHolder, + combo, + stringConverter + ); + + return combo; + } + + + /** + * Creates a new editable <code>Combo</code>. + * + * @param container The parent container + * @return The newly created <code>CCombo</code> + * + * @category Layout + */ + private Combo addUnmanagedEditableCombo(Composite container) { + + Combo combo = this.widgetFactory.createEditableCombo(container); + combo.setLayoutData(getFieldGridData()); + return combo; + } + + /** + * Creates a new container that will have a text field as the center control + * labeled with the given label. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @return The newly created <code>Text</code> + * + * @category Layout + */ + protected final Text addLabeledText(Composite container, + String labelText, + WritablePropertyValueModel<String> textHolder) { + + return this.addLabeledText(container, labelText, textHolder, null); + } + + /** + * Creates a new container that will have a text field as the center control + * labeled with the given label. + * + * @param container The parent container + * @param labelText The text field's label + * @param rightComponent The component to be placed to the right of the text + * field + * @param textHolder The holder of the text field's input + * @param helpId The topic help ID to be registered for the text field + * @return The newly created <code>Text</code> + * + * @category Layout + */ + protected final Text addLabeledText(Composite container, + String labelText, + WritablePropertyValueModel<String> textHolder, + Control rightComponent, + String helpId) { + + Text text = this.addText(container, textHolder); + + this.addLabeledComposite( + container, + labelText, + text, + rightComponent, + helpId + ); + + return text; + } + /** + * + * Creates a new container that will have a text field as the center control + * labeled with the given label. + * + * @param container The parent container + * @param labelText The text field's label + * @param rightComponent The component to be placed to the right of the text + * field + * @param textHolder The holder of the text field's input + * @param helpId The topic help ID to be registered for the text field + * @return The newly created <code>Text</code> + * + * @category Layout + */ + protected final Text addLabeledText(Composite container, + Label label, + WritablePropertyValueModel<String> textHolder, + Control rightComponent, + String helpId) { + + Text text = this.addText(container, textHolder); + + this.addLabeledComposite( + container, + label, + text, + rightComponent, + helpId + ); + + return text; + } + + /** + * Creates a new container that will have a text field as the center control + * labeled with the given label. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @param helpId The topic help ID to be registered for the text field + * @return The newly created <code>Text</code> + * + * @category Layout + */ + protected final Text addLabeledText(Composite container, + String labelText, + WritablePropertyValueModel<String> textHolder, + String helpId) { + + return this.addLabeledText( + container, + labelText, + textHolder, + null, + helpId + ); + } + + /** + * Creates a new container that will have a text field as the center control + * labeled with the given label. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @param helpId The topic help ID to be registered for the text field + * @return The newly created <code>Text</code> + * + * @category Layout + */ + protected final Text addLabeledText(Composite container, + Label label, + WritablePropertyValueModel<String> textHolder, + String helpId) { + + return this.addLabeledText( + container, + label, + textHolder, + null, + helpId + ); + } + + /** + * Creates a new list and notify the given selection holder when the + * selection changes. If the selection count is different than one than the + * holder will receive <code>null</code>. + * + * @param container The parent container + * @param helpId The topic help ID to be registered for the new radio button + * @return The newly created <code>List</code> + * + * @category Layout + */ + protected final List addList(Composite container, String helpId) { + + return this.addList( + container, + new SimplePropertyValueModel<String>(), + helpId + ); + } + + /** + * Creates a new list and notify the given selection holder when the + * selection changes. If the selection count is different than one than the + * holder will receive <code>null</code>. + * + * @param container The parent container + * @param selectionHolder The holder of the unique selected item + * @param helpId The topic help ID to be registered for the new radio button + * @return The newly created <code>List</code> + * + * @category Layout + */ + protected final List addList(Composite container, + WritablePropertyValueModel<String> selectionHolder, + String helpId) { + + List list = this.addUnmanagedList(container, selectionHolder, helpId); + this.manageWidget(list); + + return list; + } + + /** + * Creates a new unmanaged list and notify the given selection holder when the + * selection changes. If the selection count is different than one than the + * holder will receive <code>null</code>. + * Unmanaged means that this Pane will not handle the enabling/disabling of this widget. + * The owning object will handle it with its own PaneEnabler or ControlEnabler. + * + * @param container The parent container + * @param selectionHolder The holder of the unique selected item + * @param helpId The topic help ID to be registered for the new radio button + * @return The newly created <code>List</code> + * + * @category Layout + */ + private List addUnmanagedList(Composite container, + WritablePropertyValueModel<String> selectionHolder, + String helpId) { + + List list = this.widgetFactory.createList( + container, + SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI + ); + + list.addSelectionListener(buildSelectionListener(selectionHolder)); + list.setLayoutData(new GridData(GridData.FILL_BOTH)); + + if (helpId != null) { + getHelpSystem().setHelp(list, helpId); + } + + return list; + } + + /** + * Creates a new lable expanding on multiple lines. + * + * @param parent The parent container + * @param labelText The label's text + * + * @category Layout + */ + protected final FormText addMultiLineLabel(Composite container, + String labelText) { + + FormText label = this.widgetFactory.createMultiLineLabel(container, labelText); + manageWidget(label); + return label; + } + + /** + * Creates a new <code>Text</code> widget that has multiple lines. + * + * @param container The parent container + * @return The newly created <code>Text</code> widget + * + */ + protected final Text addMultiLineText(Composite container) { + + Text text = this.widgetFactory.createMultiLineText(container); + text.setLayoutData(getFieldGridData()); + this.manageWidget(text); + + return text; + } + + /** + * Creates a new <code>Text</code> widget that has multiple lines. + * + * @param container The parent container + * @param lineCount The number of lines the text area should display + * @param helpId The topic help ID to be registered for the new text + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + protected final Text addMultiLineText(Composite container, + int lineCount, + String helpId) { + + Text text = this.addMultiLineText(container); + + GridData gridData = getFieldGridData(); + gridData.heightHint = text.getLineHeight() * lineCount; + text.setLayoutData(gridData); + + if (helpId != null) { + getHelpSystem().setHelp(text, helpId); + } + + return text; + } + + /** + * Creates a new <code>Text</code> widget that has multiple lines. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @param lineCount The number of lines the text area should display + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + protected final Text addMultiLineText(Composite container, + WritablePropertyValueModel<String> textHolder, + int lineCount) { + + return this.addMultiLineText(container, textHolder, lineCount, null); + } + + /** + * Creates a new <code>Text</code> widget that has multiple lines. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @param helpId The topic help ID to be registered for the new text + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + protected final Text addMultiLineText(Composite container, + WritablePropertyValueModel<String> textHolder, + int lineCount, + String helpId) { + + Text text = this.addMultiLineText(container, lineCount, helpId); + SWTTools.bind(textHolder, text); + return text; + } + + /** + * Creates a new <code>PageBook</code> and set the proper layout and layout + * data. + * + * @param container The parent container + * @return The newly created <code>PageBook</code> + * + * @category Layout + */ + protected final PageBook addPageBook(Composite container) { + + PageBook pageBook = new PageBook(container, SWT.NULL); + pageBook.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + return pageBook; + } + + /** + * Creates a new container without specifying any layout manager. + * + * @param container The parent of the new container + * @return The newly created <code>Composite</code> + * + * @category Layout + */ + protected final Composite addPane(Composite parent) { + return this.widgetFactory.createComposite(parent); + } + + /** + * Creates a new container using the given layout manager. + * + * @param parent The parent of the new container + * @param layout The layout manager of the new container + * @return The newly created container + * + * @category Layout + */ + protected final Composite addPane(Composite container, Layout layout) { + + container = this.addPane(container); + container.setLayout(layout); + container.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + return container; + } + + /** + * Creates a new <code>Text</code> widget. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + protected final Text addPasswordText(Composite container, + WritablePropertyValueModel<String> textHolder) { + + Text text = this.addPasswordText(container); + SWTTools.bind(textHolder, text); + + return text; + } + + /** + * Creates a new <code>Text</code> widget. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + protected final Text addPasswordText(Composite container) { + + Text text = this.widgetFactory.createPasswordText(container); + text.setLayoutData(getFieldGridData()); + + this.manageWidget(text); + return text; + } + + /** + * Creates a new push button using the given information. + * + * @param parent The parent container + * @param buttonText The button's text + * @param buttonAction The action to be invoked when the button is pressed + * @return The newly created <code>Button</code> + * + * @category Layout + */ + protected final Button addPushButton(Composite parent, + String buttonText, + final Runnable buttonAction) { + + return this.addPushButton(parent, buttonText, null, buttonAction); + } + + /** + * Creates a new push button using the given information. + * + * @param parent The parent container + * @param buttonText The button's text + * @param buttonAction The action to be invoked when the button is pressed + * @param helpId The topic help ID to be registered for the new radio button + * @return The newly created <code>Button</code> + * + * @category Layout + */ + protected final Button addPushButton(Composite parent, + String buttonText, + String helpId, + final Runnable buttonAction) { + + Button button = this.widgetFactory.createPushButton(parent, buttonText); + manageWidget(button); + button.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + SWTUtil.asyncExec(buttonAction); + } + }); + + button.setLayoutData(new GridData()); + + if (helpId != null) { + getHelpSystem().setHelp(button, helpId); + } + + return button; + } + + /** + * Creates a new check box using the given information. + * + * @param parent The parent container + * @param buttonText The button's text + * @param booleanHolder The holder of the selection state + * @param helpId The topic help ID to be registered for the new radio button + * @return The newly created <code>Button</code> + * + * @category Layout + */ + protected final Button addRadioButton(Composite parent, + String buttonText, + WritablePropertyValueModel<Boolean> booleanHolder, + String helpId) { + + return this.addToggleButton( + parent, + buttonText, + booleanHolder, + helpId, + SWT.RADIO + ); + } + + /** + * Creates a new <code>Section</code>. A sub-pane is automatically added as + * its client and is the returned <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + protected final Composite addSection(Composite container, + String sectionText) { + + return this.addSection( + container, + sectionText, + ExpandableComposite.TITLE_BAR + ); + } + + /** + * Creates a new <code>Section</code>. A sub-pane is automatically added as + * its client and is the returned <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @param type The type of section to create + * @param expandedStateHolder The holder of the boolean that will dictate + * when to expand or collapse the section + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + private Composite addSection(Composite container, + String sectionText, + int type) { + + return this.addSection(container, sectionText, null, type); + } + + /** + * Creates a new <code>Section</code>. A sub-pane is automatically added as + * its client and is the returned <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @param description The section's description + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + protected final Composite addSection(Composite container, + String sectionText, + String description) { + + return this.addSection( + container, + sectionText, + description, + ExpandableComposite.TITLE_BAR + ); + } + + /** + * Creates a new <code>Section</code>. A sub-pane is automatically added as + * its client and is the returned <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @param description The section's description or <code>null</code> if none + * was provider + * @param type The type of section to create + * @param expandedStateHolder The holder of the boolean that will dictate + * when to expand or collapse the section + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + private Composite addSection(Composite container, + String sectionText, + String description, + int type) { + + Section section = this.widgetFactory.createSection(container, type | ((description != null) ? Section.DESCRIPTION : SWT.NULL)); + section.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + section.setText(sectionText); + section.marginWidth = 0; + section.marginHeight = 0; + + if (description != null) { + section.setDescription(description); + } + + Composite subPane = this.addSubPane(section); + section.setClient(subPane); + + return subPane; + } + + private SelectionListener buildSelectionListener(final WritablePropertyValueModel<String> selectionHolder) { + return new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + List list = (List) e.widget; + String[] selectedItems = list.getSelection(); + if ((selectedItems == null) || (selectedItems.length != 1)) { + selectionHolder.setValue(null); + } + else { + selectionHolder.setValue(selectedItems[0]); + } + } + }; + } + + /** + * Creates the layout responsible to compute the size of the spacer created + * for the right control when none was given. The spacer helps to align all + * the right controls. + * + * @category Layout + */ + private Layout buildSpacerLayout() { + return new Layout() { + @Override + protected Point computeSize(Composite composite, + int widthHint, + int heightHint, + boolean flushCache) { + + return new Point(widthHint, heightHint); + } + + @Override + protected void layout(Composite composite, boolean flushCache) { + GridData data = (GridData) composite.getLayoutData(); + composite.setBounds(0, 0, data.widthHint, data.heightHint); + } + }; + } + + private PropertyChangeListener buildSubjectChangeListener() { + return new SWTPropertyChangeListenerWrapper(this.buildSubjectChangeListener_()); + } + + private PropertyChangeListener buildSubjectChangeListener_() { + return new PropertyChangeListener() { + @SuppressWarnings("unchecked") + public void propertyChanged(PropertyChangeEvent e) { + Pane.this.subjectChanged((T) e.getOldValue(), (T) e.getNewValue()); + } + }; + } + + /** + * Creates a new <code>Composite</code> used as a sub-pane. + * + * @param container The parent container + * @return The newly created <code>Composite</code> used as a sub-pane + * + * @category Layout + */ + protected final Composite addSubPane(Composite container) { + return this.addSubPane(container, 0); + } + + /** + * Creates a new <code>Composite</code> used as a sub-pane. + * + * @param container The parent container + * @param topMargin The extra spacing to add at the top of the pane + * @return The newly created <code>Composite</code> used as a sub-pane + * + * @category Layout + */ + protected final Composite addSubPane(Composite container, int topMargin) { + return this.addSubPane(container, topMargin, 0); + } + + /** + * Creates a new <code>Composite</code> used as a sub-pane. + * + * @param container The parent container + * @param topMargin The extra spacing to add at the top of the pane + * @param leftMargin The extra spacing to add to the left of the pane + * @return The newly created <code>Composite</code> used as a sub-pane + * + * @category Layout + */ + protected final Composite addSubPane(Composite container, + int topMargin, + int leftMargin) { + + return this.addSubPane(container, topMargin, leftMargin, 0, 0); + } + + /** + * Creates a new <code>Composite</code> used as a sub-pane, the new widget + * will have its layout and layout data already initialized, the layout will + * be a <code>GridLayout</code> with 1 column. + * + * @param container The parent container + * @param topMargin The extra spacing to add at the top of the pane + * @param leftMargin The extra spacing to add to the left of the pane + * @param bottomMargin The extra spacing to add at the bottom of the pane + * @param rightMargin The extra spacing to add to the right of the pane + * @return The newly created <code>Composite</code> used as a sub-pane + * + * @category Layout + */ + protected final Composite addSubPane(Composite container, + int topMargin, + int leftMargin, + int bottomMargin, + int rightMargin) { + + return this.addSubPane( + container, + 1, + topMargin, + leftMargin, + bottomMargin, + rightMargin); + } + + /** + * Creates a new <code>Composite</code> used as a sub-pane, the new widget + * will have its layout and layout data already initialized, the layout will + * be a <code>GridLayout</code> with 1 column. + * + * @param container The parent container + * @param topMargin The extra spacing to add at the top of the pane + * @param leftMargin The extra spacing to add to the left of the pane + * @param bottomMargin The extra spacing to add at the bottom of the pane + * @param rightMargin The extra spacing to add to the right of the pane + * @return The newly created <code>Composite</code> used as a sub-pane + * + * @category Layout + */ + protected final Composite addSubPane(Composite container, + int columnCount, + int topMargin, + int leftMargin, + int bottomMargin, + int rightMargin) { + + GridLayout layout = new GridLayout(columnCount, false); + layout.marginHeight = 0; + layout.marginWidth = 0; + layout.marginTop = topMargin; + layout.marginLeft = leftMargin; + layout.marginBottom = bottomMargin; + layout.marginRight = rightMargin; + + container = this.addPane(container, layout); + + return container; + } + + /** + * Creates a new <code>Section</code>. A sub-pane is automatically added as + * its client which can be typed cast directly as a <code>Composite</code>. + * + * @param container The container of the new widget + * @param sectionText The text of the new section + * @return The <code>Section</code>'s sub-pane + * + * @category Layout + */ + protected final Composite addSubSection(Composite container, + String sectionText) { + + return this.addCollapsibleSubSection( + container, + sectionText, + new SimplePropertyValueModel<Boolean>(Boolean.TRUE) + ); + } + + /** + * Creates a new table. + * + * @param container The parent container + * @param style The style to apply to the table + * @param helpId The topic help ID to be registered for the new table or + * <code>null</code> if no help ID is required + * @return The newly created <code>Table</code> + * + * @category Layout + */ + protected final Table addTable(Composite container, + int style, + String helpId) { + + Table table = addUnmanagedTable(container, style, helpId); + this.manageWidget(table); + + return table; + } + /** + * Creates a new unmanaged table. Unmanaged means that this Pane will + * not handle the enabling/disabling of this widget. The owning object will handle + * it with its own PaneEnabler or ControlEnabler. + * + * @param container The parent container + * @param style The style to apply to the table + * @param helpId The topic help ID to be registered for the new table or + * <code>null</code> if no help ID is required + * @return The newly created <code>Table</code> + * + * @category Layout + */ + protected final Table addUnmanagedTable(Composite container, + int style, + String helpId) { + + Table table = this.widgetFactory.createTable(container, style); + table.setHeaderVisible(true); + table.setLinesVisible(true); + + GridData gridData = new GridData(GridData.FILL_BOTH); + gridData.heightHint = table.getItemHeight() * 4; + table.setLayoutData(gridData); + + if (helpId != null) { + getHelpSystem().setHelp(table, helpId); + } + + return table; + } + + /** + * Creates a new table. + * + * @param container The parent container + * @param helpId The topic help ID to be registered for the new table or + * <code>null</code> if no help ID is required + * @return The newly created <code>Table</code> + * + * @category Layout + */ + protected final Table addTable(Composite container, String helpId) { + + return this.addTable( + container, + SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION | SWT.MULTI, + helpId + ); + } + + /** + * Creates a new unmanaged table. Unmanaged means that this Pane will + * not handle the enabling/disabling of this widget. The owning object will handle + * it with its own PaneEnabler or ControlEnabler. + * + * @param container The parent container + * @param helpId The topic help ID to be registered for the new table or + * <code>null</code> if no help ID is required + * @return The newly created <code>Table</code> + * + * @category Layout + */ + protected final Table addUnmanagedTable(Composite container, String helpId) { + + return this.addUnmanagedTable( + container, + SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION | SWT.MULTI, + helpId + ); + } + + /** + * Creates a new managed <code>Text</code> widget. + * + * @param container The parent container + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + protected final Text addText(Composite container) { + Text text = this.addUnmanagedText(container); + this.manageWidget(text); + return text; + } + + /** + * Creates a new unmanaged <code>Text</code> widget. Unmanaged means + * that this Pane will not handle the enabling/disabling of this widget. + * The owning object will handle it with its own PaneEnabler or ControlEnabler. + * + * @param container The parent container + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + private Text addUnmanagedText(Composite container) { + Text text = this.widgetFactory.createText(container); + text.setLayoutData(getFieldGridData()); + return text; + } + + /** + * Creates a new <code>Text</code> widget. + * + * @param container The parent container + * @param helpId The topic help ID to be registered for the new text + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + protected final Text addText(Composite container, String helpId) { + + Text text = this.addText(container); + + if (helpId != null) { + getHelpSystem().setHelp(text, helpId); + } + + return text; + } + + /** + * Creates a new unmanaged <code>Text</code> widget. Unmanaged means + * that this Pane will not handle the enabling/disabling of this widget. + * The owning object will handle it with its own PaneEnabler or ControlEnabler. + * + * @param container The parent container + * @param helpId The topic help ID to be registered for the new text + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + private Text addUnmanagedText(Composite container, String helpId) { + + Text text = this.addUnmanagedText(container); + + if (helpId != null) { + getHelpSystem().setHelp(text, helpId); + } + + return text; + } + + /** + * Creates a new <code>Text</code> widget. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + protected final Text addText(Composite container, + WritablePropertyValueModel<String> textHolder) { + + return this.addText(container, textHolder, null); + } + + /** + * Creates a new <code>Text</code> widget. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @param helpId The topic help ID to be registered for the new text + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + protected final Text addText(Composite container, + WritablePropertyValueModel<String> textHolder, + String helpId) { + + Text text = this.addText(container, helpId); + SWTTools.bind(textHolder, text); + + return text; + } + + protected final Text addText( + Composite container, + WritablePropertyValueModel<String> textHolder, + String helpId, + PropertyValueModel<Boolean> enabledModel + ) { + Text text = this.addUnmanagedText(container, textHolder, helpId); + this.controlEnabledState(enabledModel, text); + return text; + } + + /** + * Creates a new unmanaged <code>Text</code> widget. Unmanaged means + * that this Pane will not handle the enabling/disabling of this widget. + * The owning object will handle it with its own PaneEnabler or ControlEnabler. + * + * @param container The parent container + * @param textHolder The holder of the text field's input + * @param helpId The topic help ID to be registered for the new text + * @return The newly created <code>Text</code> widget + * + * @category Layout + */ + private Text addUnmanagedText(Composite container, + WritablePropertyValueModel<String> textHolder, + String helpId) { + + Text text = this.addUnmanagedText(container, helpId); + SWTTools.bind(textHolder, text); + + return text; + } + + /** + * Creates a new container with a titled border. + * + * @param title The text of the titled border + * @param container The parent container + * @return The newly created <code>Composite</code> with a titled border + * + * @category Layout + */ + protected final Group addTitledGroup(Composite container, String title) { + return this.addTitledGroup(container, title, null); + } + + /** + * Creates a new container with a titled border. + * + * @param title The text of the titled border + * @param container The parent container + * @param helpId The topic help ID to be registered for the new group + * @return The newly created <code>Composite</code> with a titled border + * + * @category Layout + */ + protected final Group addTitledGroup(Composite container, + String title, + String helpId) { + + return addTitledGroup(container, title, 1, helpId); + } + + /** + * Creates a new container with a titled border. + * + * @param title The text of the titled border + * @param container The parent container + * @param helpId The topic help ID to be registered for the new group + * @return The newly created <code>Composite</code> with a titled border + * + * @category Layout + */ + protected final Group addTitledGroup(Composite container, + String title, + int columnCount, + String helpId) { + + Group group = this.widgetFactory.createGroup(container, title); + //manageWidget(group); TODO unsure if I want to manage groups, + //also should probably rename this addUnmanagedTitledPane + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + GridLayout layout = new GridLayout(columnCount, false); + layout.marginHeight = 0; + layout.marginWidth = 0; + layout.marginTop = 5; + layout.marginLeft = 5; + layout.marginBottom = 5; + layout.marginRight = 5; + group.setLayout(layout); + + if (helpId != null) { + getHelpSystem().setHelp(group, helpId); + } + + return group; + } + + /** + * Creates a new unmanaged new toggle button (radio button or check box). + * Unmanaged means that this Pane will not handle the enabling/disabling + * of this widget. The owning object will handle it with its own PaneEnabler + * or ControlEnabler. + * + * @param parent The parent container + * @param buttonText The button's text + * @param booleanHolder The holder of the selection state + * @param helpId The topic help ID to be registered for the new button + * @return The newly created <code>Button</code> + * + * @category Layout + */ + private Button addUnmanagedToggleButton( + Composite parent, + String buttonText, + WritablePropertyValueModel<Boolean> booleanHolder, + String helpId, + int toggleButtonType) { + + Button button; + + if (toggleButtonType == SWT.PUSH) { + button = this.widgetFactory.createPushButton(parent, buttonText); + } + else if (toggleButtonType == SWT.RADIO) { + button = this.widgetFactory.createRadioButton(parent, buttonText); + } + else if (toggleButtonType == SWT.CHECK) { + button = this.widgetFactory.createCheckBox(parent, buttonText); + } + else { + button = this.widgetFactory.createButton(parent, buttonText); + } + + button.setLayoutData(new GridData()); + SWTTools.bind(booleanHolder, button); + + if (helpId != null) { + getHelpSystem().setHelp(button, helpId); + } + + return button; + } + + /** + * Creates a new toggle button (radio button or check box) using the given + * information. + * + * @param parent The parent container + * @param buttonText The button's text + * @param booleanHolder The holder of the selection state + * @param helpId The topic help ID to be registered for the new button + * @return The newly created <code>Button</code> + * + * @category Layout + */ + private Button addToggleButton( + Composite parent, + String buttonText, + WritablePropertyValueModel<Boolean> booleanHolder, + String helpId, + int toggleButtonType) { + + Button button = addUnmanagedToggleButton( + parent, + buttonText, + booleanHolder, + helpId, + toggleButtonType); + this.manageWidget(button); + return button; + } + + /** + * Creates a new check box that can have 3 selection states (selected, + * unselected and partially selected. + * + * @param parent The parent container + * @param text The button's text + * @param booleanHolder The holder of the boolean value where <code>null</code> + * means partially selected + * @param helpId The topic help ID to be registered for the new check box + * @return The newly created <code>TriStateCheckBox</code> + * + * @category Layout + */ + protected final TriStateCheckBox addTriStateCheckBox(Composite parent, + String text, + WritablePropertyValueModel<Boolean> booleanHolder, + String helpId) { + + TriStateCheckBox checkBox = new TriStateCheckBox( + parent, + text, + this.getWidgetFactory() + ); + + this.manageWidget(checkBox.getCheckBox()); + + TriStateCheckBoxModelAdapter.adapt( + booleanHolder, + checkBox + ); + + if (helpId != null) { + getHelpSystem().setHelp(checkBox.getCheckBox(), helpId); + } + + return checkBox; + } + + /** + * Creates a new check box that can have 3 selection states (selected, + * unselected and partially selected. + * + * @param parent The parent container + * @param text The button's text + * @param booleanHolder The holder of the boolean value where <code>null</code> + * means partially selected + * @param stringHolder The holder of the string to put in parenthesis after + * the check box's text when it is partially selected + * @param helpId The topic help ID to be registered for the new check box + * @return The newly created <code>TriStateCheckBox</code> + * + * @category Layout + */ + protected final TriStateCheckBox addTriStateCheckBoxWithDefault(Composite parent, + String text, + WritablePropertyValueModel<Boolean> booleanHolder, + PropertyValueModel<String> stringHolder, + String helpId) { + + TriStateCheckBox checkBox = this.addTriStateCheckBox( + parent, + text, + booleanHolder, + helpId + ); + + new LabeledControlUpdater( + new LabeledButton(checkBox.getCheckBox()), + stringHolder + ); + + return checkBox; + } + + /** + * Requests this pane to populate its widgets with the subject's values. + * + * @category Populate + */ + protected void doPopulate() { + this.log(Tracing.UI_LAYOUT, " ->doPopulate()"); + } + + private void controlEnabledState(PropertyValueModel<Boolean> booleanModel, Control... controls) { + this.controlEnabledState_(this.wrapEnabledModel(booleanModel), controls); + } + + /** + * Assume the "enabled" models can return null (which is typical with aspect + * adapters etc.). + */ + private PropertyValueModel<Boolean> wrapEnabledModel(PropertyValueModel<Boolean> booleanModel) { + return new TransformationPropertyValueModel<Boolean, Boolean>(booleanModel, NonNullBooleanTransformer.FALSE); + } + + private void controlEnabledState_(PropertyValueModel<Boolean> booleanModel, Control... controls) { + SWTTools.controlEnabledState(this.andEnabledModel(booleanModel), controls); + } + + private PropertyValueModel<Boolean> getCombinedEnabledModel() { + return (this.combinedEnabledModel != null) ? this.combinedEnabledModel : this.baseEnabledModel; + } + + private boolean getCombinedEnablement() { + Boolean enabled = getCombinedEnabledModel().getValue(); + return (enabled == null) ? true : enabled.booleanValue(); + } + + private PropertyValueModel<Boolean> andEnabledModel(PropertyValueModel<Boolean> booleanModel) { + return CompositeBooleanPropertyValueModel.and(getCombinedEnabledModel(), booleanModel); + } + + protected void controllerEnablementChanged() { + enableWidgets_(getCombinedEnablement()); + } + + /** + * Changes the enablement state of the widgets of this pane. + * + * @param enabled <code>true</code> to enable the widgets or <code>false</code> + * to disable them + * + * @category Layout + */ + public void enableWidgets(boolean enabled) { + this.baseEnabledModel.setValue(Boolean.valueOf(enabled)); + enableWidgets_(getCombinedEnablement()); + } + + private void enableWidgets_(boolean enabled) { + if (! this.container.isDisposed()) { + for (Control control : this.managedWidgets) { + control.setEnabled(enabled); + } + + for (Pane<?> subPane : this.managedSubPanes) { + subPane.enableWidgets(enabled); + } + } + } + + private void engageSubjectHolder() { + this.subjectHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener); + } + + /** + * engage the specified subject + */ + protected void engageListeners(T subject) { + if (subject != null) { + this.engageListeners_(subject); + } + } + + /** + * specified subject is not null + */ + protected void engageListeners_(T subject) { + this.log(Tracing.UI_LAYOUT, " ->engageListeners_(" + subject + ')'); + + for (String propertyName : this.getPropertyNames()) { + subject.addPropertyChangeListener(propertyName, this.aspectChangeListener); + } + } + + /** + * disengage the specified subject + */ + protected void disengageListeners(T subject) { + if (subject != null) { + this.disengageListeners_(subject); + } + } + + /** + * specified subject is not null + */ + protected void disengageListeners_(T subject) { + this.log(Tracing.UI_LAYOUT, " ->disengageListeners_(" + subject + ')'); + + for (String propertyName : this.getPropertyNames()) { + subject.removePropertyChangeListener(propertyName, this.aspectChangeListener); + } + } + + private void disengageSubjectHolder() { + this.subjectHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener); + } + + /** + * Returns the main <code>Control</code> of this pane. + * + * @return The main container + * + * @category Layout + */ + public Composite getControl() { + return this.container; + } + + /** + * Returns the subject holder used by this pane. + * + * @return The holder of the subject + * + * @category Populate + */ + protected final PropertyValueModel<T> getSubjectHolder() { + return this.subjectHolder; + } + + /** + * Returns the factory responsible for creating the widgets. + * + * @return The factory used by this pane to create the widgets + * + * @category Layout + */ + protected final WidgetFactory getWidgetFactory() { + return this.widgetFactory; + } + + /** + * Returns the margin taken by a group box, which is the number of pixel the + * group box border and its margin takes before displaying its widgets plus + * 5 pixels since the widgets inside of the group box and the border should + * have that extra 5 pixels. + * + * @return The width taken by the group box border with its margin + * + * @category Layout + */ + protected final int getGroupBoxMargin() { + Group group = this.widgetFactory.createGroup(SWTUtil.getShell(), ""); + Rectangle clientArea = group.getClientArea(); + group.dispose(); + return clientArea.x + 5; + } + + /** + * Returns the helps system. + * + * @return The platform's help system + * + * @category Helper + */ + protected final IWorkbenchHelpSystem getHelpSystem() { + return PlatformUI.getWorkbench().getHelpSystem(); + } + + + /** + * Determines whether + * + * @return + * + * @category Populate + */ + protected final boolean isPopulating() { + return this.populating; + } + + /** + * Logs the given message if the <code>Tracing.DEBUG_LAYOUT</code> is enabled. + * + * @param flag + * @param message The logging message + */ + protected void log(String flag, String message) { + if (flag.equals(Tracing.UI_LAYOUT) && Tracing.booleanDebugOption(Tracing.UI_LAYOUT)) { + this.log(message); + } + } + + protected void log(String message) { + Class<?> thisClass = this.getClass(); + String className = thisClass.getSimpleName(); + + if (thisClass.isAnonymousClass()) { + className = className.substring(0, className.indexOf('$')); + className += "->" + thisClass.getSuperclass().getSimpleName(); + } + + Tracing.log(className + ": " + message); + } + + /** + * Notifies this pane to populate itself using the subject's information. + * + * @category Populate + */ + private void populate() { + if (!this.container.isDisposed()) { + this.log(Tracing.UI_LAYOUT, "populate()"); + this.repopulate(); + } + } + + /** + * Notifies the subject's property associated with the given property name + * has changed. + * + * @param propertyName The property name associated with the property change + * + * @category Populate + */ + protected void propertyChanged(String propertyName) { + } + + /** + * Returns the list of names to listen for properties changing from the + * subject. + * + * @return A non-<code>null</code> list of property names + * + * @category Populate + */ + protected Collection<String> getPropertyNames() { + ArrayList<String> propertyNames = new ArrayList<String>(); + addPropertyNames(propertyNames); + return propertyNames; + } + + /** + * Removes the given pane's widgets (those that were registered with + * its left <code>ControlAligner</code>) from this pane's left + * <code>ControlAligner</code> so that their width will no longer be adjusted + * with the width of the widest widget. + * + * @param pane The pane containing the widgets to remove + * + * @category Layout + */ + protected final void removeAlignLeft(Pane<?> pane) { + this.leftControlAligner.remove(pane.leftControlAligner); + } + + /** + * Removes the given control from the collection of widgets that are aligned + * to have the same width when they are shown to the left side of the 3 + * widget colums. + * + * @param pane The pane to remove, its width will no longer be + * ajusted to be the width of the longest widget + * + * @category Layout + */ + protected final void removeAlignLeft(Control control) { + this.leftControlAligner.remove(control); + } + + /** + * Removes the given pane's widgets (those that were registered with + * its right <code>ControlAligner</code>) from this pane's right + * <code>ControlAligner</code> so that their width will no longer be adjusted + * with the width of the widest widget. + * + * @param pane The pane containing the widgets to remove + * + * @category Layout + */ + protected final void removeAlignRight(Pane<?> pane) { + this.rightControlAligner.remove(pane.rightControlAligner); + } + + /** + * Removes the given control from the collection of widgets that are aligned + * to have the same width when they are shown to the right side of the 3 + * widget colums. + * + * @param pane The pane to remove, its width will no longer be + * ajusted to be the width of the longest widget + * + * @category Layout + */ + protected final void removeAlignRight(Control control) { + this.rightControlAligner.remove(control); + } + + /** + * Removes the given pane's controls (those that were registered for + * alignment) from this pane. + * + * @param pane The pane containing the widgets that no longer + * requires their width adjusted with the width of the longest widget + * + * @category Layout + */ + protected final void removePaneForAlignment(Pane<?> pane) { + removeAlignLeft(pane); + removeAlignRight(pane); + } + + /** + * This method is called (perhaps internally) when this needs to repopulate + * but the object of interest has not changed. + * + * @category Populate + */ + protected final void repopulate() { + + this.log(Tracing.UI_LAYOUT, " ->repopulate()"); + + // Populate this pane + try { + setPopulating(true); + doPopulate(); + } + finally { + setPopulating(false); + } + + // Ask the sub-panes to repopulate themselves + for (Pane<?> subPane : this.subPanes) { + subPane.repopulate(); + } + } + + /** + * Sets the internal flag that is used to determine whether the pane is being + * populated or not. During population, it is required to not update the + * widgets when the model is updated nor to update the model when the widgets + * are being synchronized with the model's values. + * + * @param populating + * + * @category Populate + */ + protected final void setPopulating(boolean populating) { + this.populating = populating; + } + + /** + * Either show or hides this pane. + * + * @param visible The new visibility state + */ + public void setVisible(boolean visible) { + if (!this.container.isDisposed()) { + this.container.setVisible(visible); + } + } + + /** + * Returns the nearest <code>Shell</code> displaying the main widget of this + * pane. + * + * @return The nearest window displaying this pane + */ + public final Shell getShell() { + return this.container.getShell(); + } + + /** + * Returns the subject of this pane. + * + * @return The subject if this pane was not disposed; <code>null</code> + * if it was + * + * @category Populate + */ + public T getSubject() { + return this.subjectHolder.getValue(); + } + + /** + * The subject has changed, disconnects any listeners from the old subject + * and connects those listeners onto the new subject. + * + * @param oldsubject The old subject or <code>null</code> if none was set + * @param newSubject The new subject or <code>null</code> if none needs to be + * set + * + * @category Populate + */ + protected final void subjectChanged(T oldSubject, T newSubject) { + if (!this.container.isDisposed()) { + + this.log(Tracing.UI_LAYOUT, "subjectChanged()"); + this.disengageListeners(oldSubject); + + this.repopulate(); + + this.engageListeners(newSubject); + } + } + + + /** + * Registers another <code>Pane</code> with this one so it can + * be automatically notified about certain events such as engaging or + * disengaging the listeners, etc. + * + * @param subPane The sub-pane to register + * + * @category Controller + */ + protected final void registerSubPane(Pane<?> subPane) { + this.subPanes.add(subPane); + } + + /** + * Unregisters the given <code>Pane</code> from this one so it + * can no longer be automatically notified about certain events such as + * engaging or disengaging the listeners, etc. + * + * @param subPane The sub-pane to unregister + * + * @category Controller + */ + protected final void unregisterSubPane(Pane<?> subPane) { + this.subPanes.remove(subPane); + } + + private void updatePane(String propertyName) { + if (!isPopulating() && !this.container.isDisposed()) { + this.populating = true; + + try { + propertyChanged(propertyName); + } + finally { + this.populating = false; + } + } + } + + public void dispose() { + this.log(Tracing.UI_LAYOUT, "dispose()"); + + // Dispose this pane + this.disengageListeners(getSubject()); + this.disengageSubjectHolder(); + + if (this.combinedEnabledModel != null && this.combinedEnabledModelListener != null) { + this.combinedEnabledModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.combinedEnabledModelListener); + } + + this.leftControlAligner.dispose(); + this.rightControlAligner.dispose(); + + // Ask the sub-panes to dispose themselves + for (Pane<?> subPane : this.subPanes) { + subPane.dispose(); + } + } + +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PostExecution.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PostExecution.java new file mode 100644 index 0000000000..bae4b60382 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PostExecution.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jface.dialogs.Dialog; + +/** + * This <code>PostExecution</code> is used to post execute a portion of code + * once a dialog, that was launched into a different UI thread, has been + * disposed. + * + * @version 2.0 + * @since 1.0 + */ +public interface PostExecution<T extends Dialog> { + + /** + * Notifies this post exection the dialog that was launched into a different + * UI thread has been disposed. + * + * @param dialog The dialog that was launched into a different thread + */ + public void execute(T dialog); +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PropertySheetWidgetFactory.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PropertySheetWidgetFactory.java new file mode 100644 index 0000000000..c841976fc0 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PropertySheetWidgetFactory.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory; + +/** + * This <code>WidgetFactory</code> is responsible to create the widgets using + * <code>TabbedPropertySheetWidgetFactory</code> in order use the form style + * (flat-style) look and feel. + * + * @see TabbedPropertySheetWidgetFactory + * + * @version 2.0 + * @since 2.0 + */ +public class PropertySheetWidgetFactory extends FormWidgetFactory { + + /** + * Creates a new <code>PropertySheetWidgetFactory</code>. + * + * @param widgetFactory The actual factory responsible for creating the new + * widgets + */ + public PropertySheetWidgetFactory(TabbedPropertySheetWidgetFactory widgetFactory) { + super(widgetFactory); + } + + /** + * {@inheritDoc} + */ + @Override + public Composite createComposite(Composite parent) { + return getWidgetFactory().createComposite(parent); + } + + /** + * {@inheritDoc} + */ + @Override + public Group createGroup(Composite parent, String title) { + return getWidgetFactory().createGroup(parent, title); + } + + /** + * {@inheritDoc} + */ + @Override + public TabbedPropertySheetWidgetFactory getWidgetFactory() { + return (TabbedPropertySheetWidgetFactory) super.getWidgetFactory(); + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/TriStateCheckBox.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/TriStateCheckBox.java new file mode 100644 index 0000000000..ecb0d2098c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/TriStateCheckBox.java @@ -0,0 +1,287 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import org.eclipse.jpt.common.ui.WidgetFactory; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; + +/** + * This <code>TriStateCheckBox</code> can display one of three states: + * unchecked, checked, or partially checked. It can be modified via a mouse + * selection, via a keyboard selection, or programmatically. The selection state is + * represented by a <code>Boolean</code> value where a <code>null</code> + * value means partially checked. + * <p> + * The order of state changes is: unchecked -> partially checked -> checked. + * + * @version 2.0 + * @since 2.0 + */ +@SuppressWarnings("nls") +public final class TriStateCheckBox +{ + /** + * A check box button. + */ + private final Button button; + + /** + * The current selection state. + */ + private TriState state; + + /** + * Creates a new <code>TriStateCheckBox</code> with no text. + * + * @param parent The parent composite + * @param widgetFactory The factory used to create the check box + */ + public TriStateCheckBox(Composite parent, WidgetFactory widgetFactory) { + this(parent, null, widgetFactory); + } + + /** + * Creates a new <code>TriStateCheckBox</code>. + * + * @param parent The parent composite + * @param text The check box's text + * @param widgetFactory The factory used to create the check box + */ + public TriStateCheckBox(Composite parent, + String text, + WidgetFactory widgetFactory) { + super(); + this.state = TriState.UNCHECKED; + this.button = widgetFactory.createCheckBox(parent, text); + this.button.addSelectionListener(this.buildSelectionListener()); + } + + /** + * Convenience method: Adds a dispose listener to the check box. + * The source of any events sent to the listener will be the check box widget. + */ + public void addDisposeListener(DisposeListener disposeListener) { + this.button.addDisposeListener(disposeListener); + } + + /** + * Convenience method: Adds a selection listener to the check box. + * The source of any events sent to the listener will be the check box widget. + */ + public void addSelectionListener(SelectionListener selectionListener) { + this.button.addSelectionListener(selectionListener); + } + + private SelectionListener buildSelectionListener() { + return new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + TriStateCheckBox.this.checkBoxClicked(); + } + }; + } + + /** + * The check box was clicked, change the tri-state to the next value and + * update the check box's state. + */ + void checkBoxClicked() { + this.state = this.nextState(); + this.updateCheckBox(); + } + + /** + * Disposes the check box widget. + */ + public void dispose() { + this.button.dispose(); + } + + /** + * Returns the <code>Button</code> used to show a tri-state check box. + * + * @return The <code>Button</code> used to show a tri-state check box + */ + public Button getCheckBox() { + return this.button; + } + + /** + * Returns the check box's image. + * + * @return The check box's image. + */ + public Image getImage() { + return this.button.getImage(); + } + + /** + * Returns the check box's selection state. + * + * @return Either <code>true</code> or <code>false</code> for checked or + * unchecked; or <code>null</code> for partially selected + */ + public Boolean getSelection() { + return (this.state == TriState.PARTIALLY_CHECKED) ? null : Boolean.valueOf(this.state == TriState.CHECKED); + } + + /** + * Returns the check box's text. + * + * @return The text of the check box + */ + public String getText() { + return this.button.getText(); + } + + /** + * Returns whether the check box is disposed. + * + * @return <code>true</code> if the check box is disposed; <code>false</code> + * otherwise + */ + public boolean isDisposed() { + return this.button.isDisposed(); + } + + /** + * Returns whether the check box is enabled. + * + * @return <code>true</code> if the check box is enabled; <code>false</code> + * otherwise + */ + public boolean isEnabled() { + return this.button.isEnabled(); + } + + /** + * Returns the next state: + * UNCHECKED -> PARTIALLY_CHECKED + * PARTIALLY_CHECKED -> CHECKED + * CHECKED -> UNCHECKED + */ + private TriState nextState() { + switch (this.state) { + case UNCHECKED: + return TriState.PARTIALLY_CHECKED; + case PARTIALLY_CHECKED: + return TriState.CHECKED; + case CHECKED: + return TriState.UNCHECKED; + default: + throw new IllegalStateException("unknown state: " + this.state); + } + } + + /** + * Convenience method: Removes a dispose listener from the check box. + */ + public void removeDisposeListener(DisposeListener disposeListener) { + this.button.removeDisposeListener(disposeListener); + } + + /** + * Convenience method: Removes a selection listener from the check box. + */ + public void removeSelectionListener(SelectionListener selectionListener) { + this.button.removeSelectionListener(selectionListener); + } + + /** + * Changes the check box's enablement state. + * + * @param enabled <code>true</code> to enable the check box or <code>false</code> + * to disable it + */ + public void setEnabled(boolean enabled) { + this.button.setEnabled(enabled); + } + + /** + * Sets the check box's image. + * + * @param image The new image of the check box + */ + public void setImage(Image image) { + this.button.setImage(image); + } + + /** + * Changes the check box's selection state. + * + * @param selection Either <code>true</code> or <code>false</code> for + * checked and unchecked; or <code>null</code> for partially selected + */ + public void setSelection(Boolean selection) { + TriState old = this.state; + this.state = this.stateForBoolean(selection); + if (old != this.state) { + this.updateCheckBox(); + } + } + + /** + * Sets the check box's text. + * + * @param text The new text of the check box + */ + public void setText(String text) { + this.button.setText(text); + } + + /** + * Returns the tri-state corresponding to the boolean. + * + * @param selection The boolean to be converted to a tri-state + */ + private TriState stateForBoolean(Boolean selection) { + return (selection == null) ? TriState.PARTIALLY_CHECKED : + selection.booleanValue() ? TriState.CHECKED : TriState.UNCHECKED; + } + + /** + * Updates the selection state of the of the check box based on the tri-state + * value. + */ + void updateCheckBox() { + switch (this.state) { + case UNCHECKED: + this.button.setSelection(false); + this.button.setGrayed(false); + break; + case PARTIALLY_CHECKED: + this.button.setSelection(true); + this.button.setGrayed(true); + break; + case CHECKED: + this.button.setSelection(true); + this.button.setGrayed(false); + break; + default: + throw new IllegalStateException("unknown state: " + this.state); + } + } + + /** + * An enum containing the possible selections. + */ + enum TriState { + CHECKED, + PARTIALLY_CHECKED, + UNCHECKED + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ValidatingDialog.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ValidatingDialog.java new file mode 100644 index 0000000000..b257fac8a5 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ValidatingDialog.java @@ -0,0 +1,233 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.internal.widgets; + +import java.util.ListIterator; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jpt.utility.internal.node.Node; +import org.eclipse.jpt.utility.internal.node.Problem; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Shell; + +/** + * This dialog is similar to it superclass, <code>Dialog</code>, with + * the added value of an error message label below the main panel. A subclass + * can set this error message as needed so that it can inform the user something + * incorrect has been entered. + * <p> + * If there is an error message, it will be shown. If there is a warning + * message, it will only be shown if there is no error message. Warning messages + * have a different icon than error messages. + * + * @version 2.0 + * @since 2.0 + */ +public abstract class ValidatingDialog<T extends Node> extends Dialog<T> { + + /** + * Creates a new <code>ValidatingDialog</code>. + * + * @param parent The parent shell + */ + public ValidatingDialog(Shell parent) { + super(parent); + } + + /** + * Creates a new <code>ValidatingDialog</code>. + * + * @param parent The parent shell + * @param title The dialog's title + */ + public ValidatingDialog(Shell parent, String title) { + super(parent, title); + } + + /* + * (non-Javadoc) + */ + @Override + final Node.Validator buildValidator() { + return new Node.Validator() { + public void pause() { + } + + public void resume() { + } + + public void validate() { + ValidatingDialog.this.validate(); + } + }; + } + + /** + * Clears the messages from the description pane + */ + protected final void clearMessage(){ + setMessage(getDescription()); + } + + /** + * Clears the error message from the description pane. + */ + protected final void clearErrorMessage() { + setErrorMessage(null); + } + + /** + * Returns the description shown in the description pane. + * + * @return The description under the description's title + */ + protected String getDescription() { + return null; + } + + /** + * Returns the image shown in the description pane. + * + * @return The image of the description pane or <code>null</code> if none is + * required + */ + protected Image getDescriptionImage() { + return null; + } + + /** + * Returns the title of the description pane. + * + * @return The title shown in the description pane + */ + protected String getDescriptionTitle() { + return null; + } + + /* + * (non-Javadoc) + */ + @Override + protected Point getInitialSize() { + Point result = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT); + Point paneSize = getPane().getControl().computeSize(SWT.DEFAULT, SWT.DEFAULT); + int width = convertHorizontalDLUsToPixels(400); + result.x = Math.max(width, paneSize.x); + return result; + } + + /* + * (non-Javadoc) + */ + @Override + protected final boolean hasTitleArea() { + return true; + } + + /* + * (non-Javadoc) + */ + @Override + protected void initializeUI() { + + super.initializeUI(); + + // Update the description title + String descriptionTitle = getDescriptionTitle(); + + if (descriptionTitle != null) { + setTitle(descriptionTitle); + } + + // Update the description title + String description = getDescription(); + + if (description != null) { + setMessage(description); + } + + // Update the description image + Image image = getDescriptionImage(); + + if (image != null) { + setTitleImage(image); + } + } + + /** + * Updates the description pane by showing the given error message and format + * the text with the given list of arguments if any exists. + * + * @param errorMessage The error message to show in the description pane + * @param arguments The list of arguments used to format the error message + */ + protected final void setErrorMessage(String errorMessage, Object... arguments) { + setErrorMessage(NLS.bind(errorMessage, arguments)); + } + + /** + * Updates the description pane by showing the given warning message and format + * the text with the given list of arguments if any exists. + * + * @param warningMessage The warning message to show in the description pane + * @param arguments The list of arguments used to format the error message + */ + + protected final void setWarningMessage(String warningMessage, Object... arguments) { + setMessage(NLS.bind(warningMessage, arguments), IMessageProvider.WARNING); + } + + /** + * Updates the error message, either shows the first error problem or hides + * the error pane. If the progress bar is shown, then the error message will + * not be shown. + */ + private void updateMessage() { + if (getSubject().branchProblemsSize() == 0) { + clearMessage(); + } else { + for (ListIterator<Problem> problems = getSubject().branchProblems(); problems.hasNext();){ + Problem problem = problems.next(); + if (problem.messageType() == IMessageProvider.ERROR){ + this.setErrorMessage(problem.messageKey(), problem.messageArguments()); + } + else if (problem.messageType() == IMessageProvider.WARNING){ + this.setWarningMessage(problem.messageKey(), problem.messageArguments()); + } + } + } + if (!this.containsErrorMessage()){ + clearErrorMessage(); + } + } + + public final boolean containsErrorMessage(){ + boolean error = false; + for (ListIterator<Problem> problems = getSubject().branchProblems(); problems.hasNext();){ + Problem problem = problems.next(); + if (problem.messageType() ==IMessageProvider.ERROR){ + error = true; + } + } + return error; + } + /** + * Validates the state object and based on its status, update the description + * pane to show the first error if any exists and update the enablement of + * the OK button. + */ + private void validate() { + getSubject().validateBranch(); + updateMessage(); + getButton(OK).setEnabled(!containsErrorMessage()); + } +}
\ No newline at end of file diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/DelegatingContentAndLabelProvider.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/DelegatingContentAndLabelProvider.java new file mode 100644 index 0000000000..66f3be391d --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/DelegatingContentAndLabelProvider.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.jface; + +import java.util.HashMap; +import java.util.Map; +import org.eclipse.jface.viewers.BaseLabelProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.LabelProviderChangedEvent; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.navigator.IDescriptionProvider; + +/** + * Implementation of {@link IStructuredContentProvider} and {@link ILabelProvider} that + * maintains a collection (Map, actually) of {@link ItemContentProvider} + * delegates that perform the function of providing content and label information + * for each represented item + * + * NB: This class, if used as a label provider *MUST* be used as a content provider + * for the same viewer. It may be used as a content provider with a different + * label provider, however. + * + * Provisional API: This class is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public abstract class DelegatingContentAndLabelProvider + extends BaseLabelProvider + implements IStructuredContentProvider, ILabelProvider, IDescriptionProvider +{ + private final ItemContentProviderFactory itemContentProviderFactory; + + private final ItemLabelProviderFactory itemLabelProviderFactory; + + private final Map<Object, ItemContentProvider> itemContentProviders; + + private final Map<Object, ItemLabelProvider> itemLabelProviders; + + StructuredViewer viewer; + + + protected DelegatingContentAndLabelProvider( + ItemContentProviderFactory itemContentProviderFactory) { + this(itemContentProviderFactory, null); + } + + protected DelegatingContentAndLabelProvider( + ItemContentProviderFactory itemContentProviderFactory, + ItemLabelProviderFactory itemLabelProviderFactory) { + super(); + this.itemContentProviderFactory = itemContentProviderFactory; + this.itemLabelProviderFactory = itemLabelProviderFactory; + this.itemContentProviders = new HashMap<Object, ItemContentProvider>(); + this.itemLabelProviders = new HashMap<Object, ItemLabelProvider>(); + } + + + protected ItemContentProvider itemContentProvider(Object item) { + ItemContentProvider itemContentProvider = this.itemContentProviders.get(item); + if (itemContentProvider != null) { + return itemContentProvider; + } + itemContentProvider = this.itemContentProviderFactory.buildItemContentProvider(item, this); + if (itemContentProvider == null) { + return null; + } + this.itemContentProviders.put(item, itemContentProvider); + return itemContentProvider; + } + + protected ItemLabelProvider itemLabelProvider(Object item) { + if (this.viewer == null) { + throw new IllegalStateException( + "This provider must be used as a content" + //$NON-NLS-1$ + "provider *as well as* a label provider."); //$NON-NLS-1$ + } + ItemLabelProvider itemLabelProvider = this.itemLabelProviders.get(item); + if (itemLabelProvider != null) { + return itemLabelProvider; + } + itemLabelProvider = this.itemLabelProviderFactory.buildItemLabelProvider(item, this); + if (itemLabelProvider == null) { + return null; + } + this.itemLabelProviders.put(item, itemLabelProvider); + return itemLabelProvider; + } + + + public Object[] getElements(Object inputElement) { + return itemContentProvider(inputElement).getElements(); + } + + public Image getImage(Object element) { + ItemLabelProvider provider = itemLabelProvider(element); + return (provider == null) ? null :provider.getImage(); + } + + public String getText(Object element) { + ItemLabelProvider provider = itemLabelProvider(element); + return (provider == null) ? null : provider.getText(); + } + + public String getDescription(Object element) { + ItemLabelProvider provider = itemLabelProvider(element); + return (provider == null) ? null : provider.getDescription(); + } + + /** + * Disposes all items + */ + @Override + public void dispose() { + disposeProviders(); + super.dispose(); + } + + protected void disposeProviders() { + // coded this way to allow some item providers to dispose of their child + // elements without disrupting the entire process + while (! this.itemContentProviders.isEmpty()) { + dispose(this.itemContentProviders.keySet().iterator().next()); + } + // this catches any items that weren't disposed from the content providers, + // though there most likely won't be any items represented here that + // haven't already been disposed + while (! this.itemLabelProviders.isEmpty()) { + dispose(this.itemLabelProviders.keySet().iterator().next()); + } + } + + /** + * Disposes item + */ + public void dispose(Object item) { + if (this.itemContentProviders.containsKey(item)) { + this.itemContentProviders.get(item).dispose(); + this.itemContentProviders.remove(item); + } + if (this.itemLabelProviders.containsKey(item)) { + this.itemLabelProviders.get(item).dispose(); + this.itemLabelProviders.remove(item); + } + } + + public void inputChanged(Viewer structuredViewer, Object oldInput, Object newInput) { + if (oldInput != newInput) { + disposeProviders(); + } + this.viewer = (StructuredViewer) structuredViewer; + } + + /** + * Update the content for the given item + */ + public void updateContent(final Object item) { + Runnable runnable = new Runnable() { + public void run() { + if (viewer != null && viewer.getControl() != null && !viewer.getControl().isDisposed()) { + viewer.refresh(item); + } + } + }; + if (!viewerIsDisposed()) { + this.viewer.getControl().getDisplay().asyncExec(runnable); + } + } + + // open up visibility a bit for inner classes + @Override + protected void fireLabelProviderChanged(LabelProviderChangedEvent event) { + super.fireLabelProviderChanged(event); + } + + /** + * Update the label for the given item + */ + public void updateLabel(final Object item) { + Runnable runnable = new Runnable() { + public void run() { + if (viewer != null && viewer.getControl() != null && !viewer.getControl().isDisposed()) { + fireLabelProviderChanged(new LabelProviderChangedEvent(DelegatingContentAndLabelProvider.this, item)); + } + } + }; + if (!viewerIsDisposed()) { + this.viewer.getControl().getDisplay().asyncExec(runnable); + } + } + + protected boolean viewerIsDisposed() { + return this.viewer.getControl().isDisposed(); + } + +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemContentProvider.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemContentProvider.java new file mode 100644 index 0000000000..2f21206f8c --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemContentProvider.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.jface; + +/** + * Marker interface used in conjunction with DelegatingContentProvider to return + * content information for a particular item. + * @see DelegatingContentProvider + * @see ItemContentProviderFactory + * + * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface ItemContentProvider +{ + /** + * Return the elements of the represented item. + * Note that when this is called, the represented item is an input element. + */ + Object[] getElements(); + + /** + * Dispose of this content provider, cleaning up all references, listeners, etc. + */ + void dispose(); +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemContentProviderFactory.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemContentProviderFactory.java new file mode 100644 index 0000000000..a9c951d816 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemContentProviderFactory.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.jface; + +/** + * Factory interface used to describe how to build {@link ItemContentProvider}s + * for a {@link DelegatingContentAndLabelProvider} + * + * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface ItemContentProviderFactory +{ + ItemContentProvider buildItemContentProvider(Object item, + DelegatingContentAndLabelProvider contentAndLabelProvider); +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemLabelProvider.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemLabelProvider.java new file mode 100644 index 0000000000..592a8a6bbe --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemLabelProvider.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.jface; + +import org.eclipse.swt.graphics.Image; + +/** + * Interface used in conjunction with DelegatingLabelProvider to return + * label information for a particular item. + * @see DelegatingLabelProvider + * @see ItemLabelProviderFactory + * + * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface ItemLabelProvider +{ + /** + * Return the image for the item + */ + Image getImage(); + + /** + * Return the text for the item + */ + public String getText(); + + /** + * Return the description for the item + */ + public String getDescription(); + + /** + * Dispose of this label provider, cleaning up all references, listeners, etc. + */ + void dispose(); +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemLabelProviderFactory.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemLabelProviderFactory.java new file mode 100644 index 0000000000..db61b3905f --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemLabelProviderFactory.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.jface; + +/** + * Factory interface used to describe how to build IItemLabelProviders + * for a DelegatingContentAndLabelProvider + * + * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface ItemLabelProviderFactory +{ + ItemLabelProvider buildItemLabelProvider(Object item, + DelegatingContentAndLabelProvider contentAndLabelProvider); +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/TreeItemContentProvider.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/TreeItemContentProvider.java new file mode 100644 index 0000000000..2e978f00b8 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/TreeItemContentProvider.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.jface; + +/** + * Interface used in conjunction with DelegatingTreeContentProvider to return + * tree information for a particular item. + * @see DelegatingTreeContentProvider + * @see TreeItemContentProviderFactory + * + * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface TreeItemContentProvider extends ItemContentProvider +{ + /** + * Return the parent of the represented item + */ + Object getParent(); + + /** + * Return whether the represented item has children + */ + boolean hasChildren(); + + /** + * Return the children of the represented item + */ + Object[] getChildren(); +} diff --git a/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/TreeItemContentProviderFactory.java b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/TreeItemContentProviderFactory.java new file mode 100644 index 0000000000..393a737525 --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/TreeItemContentProviderFactory.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Oracle. 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: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.ui.jface; + +/** + * Extension of {@link ItemContentProviderFactory} that extends functionality + * for tree content + * + * Provisional API: This interface is part of an interim API that is still + * under development and expected to change significantly before reaching + * stability. It is available at this early stage to solicit feedback from + * pioneering adopters on the understanding that any code that uses this API + * will almost certainly be broken (repeatedly) as the API evolves. + */ +public interface TreeItemContentProviderFactory extends ItemContentProviderFactory +{ + public TreeItemContentProvider buildItemContentProvider(Object item, + DelegatingContentAndLabelProvider contentAndLabelProvider); +} |