Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkmoore2011-01-31 21:48:09 +0000
committerkmoore2011-01-31 21:48:09 +0000
commitd573fb171a36de122ae964b913a1a8b6e1c95e7e (patch)
tree33bf57a8bd9f893f51498b332504d19e67e31ee1 /common/plugins/org.eclipse.jpt.common.ui
parent750f1789146ac827adb3667026b96a794fdcb875 (diff)
downloadwebtools.dali-d573fb171a36de122ae964b913a1a8b6e1c95e7e.tar.gz
webtools.dali-d573fb171a36de122ae964b913a1a8b6e1c95e7e.tar.xz
webtools.dali-d573fb171a36de122ae964b913a1a8b6e1c95e7e.zip
Phase 1 - refactoring to org.eclipse.jpt.common.*
Diffstat (limited to 'common/plugins/org.eclipse.jpt.common.ui')
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/.classpath13
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/.project28
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/.settings/org.eclipse.jdt.core.prefs8
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/META-INF/MANIFEST.MF36
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/about.html34
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/build.properties19
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/plugin.properties23
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/property_files/jpt_common_ui.properties28
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/JptCommonUiPlugin.java65
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/WidgetFactory.java240
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/JptCommonUiMessages.java53
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/Tracing.java161
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/AbstractItemLabelProvider.java224
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/AbstractTreeItemContentProvider.java204
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/ArchiveFileViewerFilter.java71
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/DelegatingTreeContentAndLabelProvider.java58
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/ImageImageDescriptor.java47
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/NullLabelProvider.java60
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/NullTreeContentProvider.java60
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/jface/StructuredContentProviderAdapter.java265
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTCollectionChangeListenerWrapper.java151
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTListChangeListenerWrapper.java201
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTPropertyChangeListenerWrapper.java82
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTStateChangeListenerWrapper.java79
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/listeners/SWTTreeChangeListenerWrapper.java151
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/properties/JptProjectPropertiesPage.java432
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/AbstractComboModelAdapter.java699
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/ColumnAdapter.java55
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/ComboModelAdapter.java210
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/DateTimeModelAdapter.java352
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/SpinnerModelAdapter.java214
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TableItemModelAdapter.java209
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TableModelAdapter.java746
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/swt/TriStateCheckBoxModelAdapter.java188
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/ControlAligner.java913
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/ControlSwitcher.java130
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledButton.java64
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledControl.java37
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledControlUpdater.java130
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/LabeledLabel.java64
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/PaneEnabler.java175
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/PaneVisibilityEnabler.java173
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/SWTUtil.java447
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/StateController.java320
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/util/TableLayoutComposite.java207
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/AsynchronousUiCommandExecutor.java49
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/SynchronousUiCommandExecutor.java49
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/AbstractListWidgetAdapter.java42
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/BooleanButtonModelBinding.java190
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/BooleanStateController.java188
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/DropDownListBoxSelectionBinding.java283
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/ListBoxSelectionBinding.java305
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/ListWidgetModelBinding.java428
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/MultiControlBooleanStateController.java157
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTComboAdapter.java67
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTListAdapter.java49
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SWTTools.java392
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/SimpleBooleanStateController.java68
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/utility/swt/TextFieldModelBinding.java196
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemoveListPane.java554
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemovePane.java923
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/AddRemoveTablePane.java314
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ChooserPane.java167
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ClassChooserComboPane.java92
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ClassChooserPane.java368
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ComboPane.java289
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/DefaultWidgetFactory.java260
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/Dialog.java350
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/DialogPane.java109
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumComboViewer.java369
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumDialogComboViewer.java77
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/EnumFormComboViewer.java77
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FileChooserComboPane.java105
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FileChooserPane.java167
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FolderChooserComboPane.java106
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FolderChooserPane.java140
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/FormWidgetFactory.java338
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/IntegerCombo.java188
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameDialog.java166
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameDialogBuilder.java179
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NewNameStateObject.java146
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/NullPostExecution.java56
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PackageChooserPane.java237
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/Pane.java3722
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PostExecution.java31
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/PropertySheetWidgetFactory.java61
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/TriStateCheckBox.java287
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/internal/widgets/ValidatingDialog.java233
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/DelegatingContentAndLabelProvider.java207
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemContentProvider.java36
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemContentProviderFactory.java26
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemLabelProvider.java47
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/ItemLabelProviderFactory.java26
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/TreeItemContentProvider.java40
-rw-r--r--common/plugins/org.eclipse.jpt.common.ui/src/org/eclipse/jpt/common/ui/jface/TreeItemContentProviderFactory.java26
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<