Merge "Add WindowBuilder propertysheet code. See README.txt for details."
diff --git a/propertysheet/.classpath b/propertysheet/.classpath
new file mode 100644
index 0000000..9d19fa9
--- /dev/null
+++ b/propertysheet/.classpath
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/guava-tools/guava-10.0.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/guava-tools/src.zip"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/linux-x86/swt/swt.jar"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.core.commands_3.6.0.I20100512-1500.jar"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.core.expressions_3.4.200.v20100505.jar"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.core.runtime_3.6.0.v20100505.jar"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.equinox.common_3.6.0.v20100503.jar"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.jface_3.6.2.M20110210-1200.jar"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.osgi_3.6.2.R36x_v20110210.jar"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.ui.workbench.texteditor_3.6.1.r361_v20100714-0800.jar"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.ui.workbench_3.6.2.M20110210-1200.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/propertysheet/.gitignore b/propertysheet/.gitignore
new file mode 100644
index 0000000..fe99505
--- /dev/null
+++ b/propertysheet/.gitignore
@@ -0,0 +1,2 @@
+bin
+
diff --git a/propertysheet/.project b/propertysheet/.project
new file mode 100644
index 0000000..2c3adbb
--- /dev/null
+++ b/propertysheet/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>propertysheet</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/propertysheet/.settings/org.eclipse.jdt.core.prefs b/propertysheet/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..4318953
--- /dev/null
+++ b/propertysheet/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,94 @@
+#Sat Mar 17 18:51:09 PDT 2012
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled
+org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/propertysheet/Android.mk b/propertysheet/Android.mk
new file mode 100644
index 0000000..b02fc80
--- /dev/null
+++ b/propertysheet/Android.mk
@@ -0,0 +1,39 @@
+#
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_JAVA_RESOURCE_DIRS := src
+
+LOCAL_JAVA_LIBRARIES := \
+	swt \
+	org.eclipse.core.commands_3.6.0.I20100512-1500 \
+	org.eclipse.core.expressions_3.4.200.v20100505 \
+	org.eclipse.core.runtime_3.6.0.v20100505 \
+	org.eclipse.equinox.common_3.6.0.v20100503 \
+	org.eclipse.jface_3.6.2.M20110210-1200 \
+	org.eclipse.osgi_3.6.2.R36x_v20110210 \
+        org.eclipse.ui.workbench.texteditor_3.6.1.r361_v20100714-0800 \
+	org.eclipse.ui.workbench_3.6.2.M20110210-1200 \
+        asm-tools \
+        guava-tools
+
+LOCAL_MODULE := propertysheet
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/propertysheet/MODULE_LICENSE_EPL b/propertysheet/MODULE_LICENSE_EPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/propertysheet/MODULE_LICENSE_EPL
diff --git a/propertysheet/NOTICE b/propertysheet/NOTICE
new file mode 100644
index 0000000..0d347ab
--- /dev/null
+++ b/propertysheet/NOTICE
@@ -0,0 +1,224 @@
+
+    Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF
+THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+*1. DEFINITIONS*
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and
+documentation distributed under this Agreement, and
+
+b) in the case of each subsequent Contributor:
+
+i) changes to the Program, and
+
+ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from and
+are distributed by that particular Contributor. A Contribution
+'originates' from a Contributor if it was added to the Program by such
+Contributor itself or anyone acting on such Contributor's behalf.
+Contributions do not include additions to the Program which: (i) are
+separate modules of software distributed in conjunction with the Program
+under their own license agreement, and (ii) are not derivative works of
+the Program.
+
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which
+are necessarily infringed by the use or sale of its Contribution alone
+or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this
+Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement,
+including all Contributors.
+
+*2. GRANT OF RIGHTS*
+
+a) Subject to the terms of this Agreement, each Contributor hereby
+grants Recipient a non-exclusive, worldwide, royalty-free copyright
+license to reproduce, prepare derivative works of, publicly display,
+publicly perform, distribute and sublicense the Contribution of such
+Contributor, if any, and such derivative works, in source code and
+object code form.
+
+b) Subject to the terms of this Agreement, each Contributor hereby
+grants Recipient a non-exclusive, worldwide, royalty-free patent license
+under Licensed Patents to make, use, sell, offer to sell, import and
+otherwise transfer the Contribution of such Contributor, if any, in
+source code and object code form. This patent license shall apply to the
+combination of the Contribution and the Program if, at the time the
+Contribution is added by the Contributor, such addition of the
+Contribution causes such combination to be covered by the Licensed
+Patents. The patent license shall not apply to any other combinations
+which include the Contribution. No hardware per se is licensed hereunder.
+
+c) Recipient understands that although each Contributor grants the
+licenses to its Contributions set forth herein, no assurances are
+provided by any Contributor that the Program does not infringe the
+patent or other intellectual property rights of any other entity. Each
+Contributor disclaims any liability to Recipient for claims brought by
+any other entity based on infringement of intellectual property rights
+or otherwise. As a condition to exercising the rights and licenses
+granted hereunder, each Recipient hereby assumes sole responsibility to
+secure any other intellectual property rights needed, if any. For
+example, if a third party patent license is required to allow Recipient
+to distribute the Program, it is Recipient's responsibility to acquire
+that license before distributing the Program.
+
+d) Each Contributor represents that to its knowledge it has sufficient
+copyright rights in its Contribution, if any, to grant the copyright
+license set forth in this Agreement.
+
+*3. REQUIREMENTS*
+
+A Contributor may choose to distribute the Program in object code form
+under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+
+b) its license agreement:
+
+i) effectively disclaims on behalf of all Contributors all warranties
+and conditions, express and implied, including warranties or conditions
+of title and non-infringement, and implied warranties or conditions of
+merchantability and fitness for a particular purpose;
+
+ii) effectively excludes on behalf of all Contributors all liability for
+damages, including direct, indirect, special, incidental and
+consequential damages, such as lost profits;
+
+iii) states that any provisions which differ from this Agreement are
+offered by that Contributor alone and not by any other party; and
+
+iv) states that source code for the Program is available from such
+Contributor, and informs licensees how to obtain it in a reasonable
+manner on or through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained
+within the Program.
+
+Each Contributor must identify itself as the originator of its
+Contribution, if any, in a manner that reasonably allows subsequent
+Recipients to identify the originator of the Contribution.
+
+*4. COMMERCIAL DISTRIBUTION*
+
+Commercial distributors of software may accept certain responsibilities
+with respect to end users, business partners and the like. While this
+license is intended to facilitate the commercial use of the Program, the
+Contributor who includes the Program in a commercial product offering
+should do so in a manner which does not create potential liability for
+other Contributors. Therefore, if a Contributor includes the Program in
+a commercial product offering, such Contributor ("Commercial
+Contributor") hereby agrees to defend and indemnify every other
+Contributor ("Indemnified Contributor") against any losses, damages and
+costs (collectively "Losses") arising from claims, lawsuits and other
+legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such
+Commercial Contributor in connection with its distribution of the
+Program in a commercial product offering. The obligations in this
+section do not apply to any claims or Losses relating to any actual or
+alleged intellectual property infringement. In order to qualify, an
+Indemnified Contributor must: a) promptly notify the Commercial
+Contributor in writing of such claim, and b) allow the Commercial
+Contributor to control, and cooperate with the Commercial Contributor
+in, the defense and any related settlement negotiations. The Indemnified
+Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those
+performance claims and warranties, and if a court requires any other
+Contributor to pay any damages as a result, the Commercial Contributor
+must pay those damages.
+
+*5. NO WARRANTY*
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED
+ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES
+OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR
+A PARTICULAR PURPOSE. Each Recipient is solely responsible for
+determining the appropriateness of using and distributing the Program
+and assumes all risks associated with its exercise of rights under this
+Agreement , including but not limited to the risks and costs of program
+errors, compliance with applicable laws, damage to or loss of data,
+programs or equipment, and unavailability or interruption of operations.
+
+*6. DISCLAIMER OF LIABILITY*
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+*7. GENERAL*
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further action
+by the parties hereto, such provision shall be reformed to the minimum
+extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including
+a cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails
+to comply with any of the material terms or conditions of this Agreement
+and does not cure such failure in a reasonable period of time after
+becoming aware of such noncompliance. If all Recipient's rights under
+this Agreement terminate, Recipient agrees to cease use and distribution
+of the Program as soon as reasonably practicable. However, Recipient's
+obligations under this Agreement and any licenses granted by Recipient
+relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement,
+but in order to avoid inconsistency the Agreement is copyrighted and may
+only be modified in the following manner. The Agreement Steward reserves
+the right to publish new versions (including revisions) of this
+Agreement from time to time. No one other than the Agreement Steward has
+the right to modify this Agreement. The Eclipse Foundation is the
+initial Agreement Steward. The Eclipse Foundation may assign the
+responsibility to serve as the Agreement Steward to a suitable separate
+entity. Each new version of the Agreement will be given a distinguishing
+version number. The Program (including Contributions) may always be
+distributed subject to the version of the Agreement under which it was
+received. In addition, after a new version of the Agreement is
+published, Contributor may elect to distribute the Program (including
+its Contributions) under the new version. Except as expressly stated in
+Sections 2(a) and 2(b) above, Recipient receives no rights or licenses
+to the intellectual property of any Contributor under this Agreement,
+whether expressly, by implication, estoppel or otherwise. All rights in
+the Program not expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the
+intellectual property laws of the United States of America. No party to
+this Agreement will bring a legal action under this Agreement more than
+one year after the cause of action arose. Each party waives its rights
+to a jury trial in any resulting litigation.
+
diff --git a/propertysheet/README.txt b/propertysheet/README.txt
new file mode 100644
index 0000000..6c2feab
--- /dev/null
+++ b/propertysheet/README.txt
@@ -0,0 +1,105 @@
+WINDOWBUILDER PROPERTY SHEET LIBRARY
+-------------------------------------
+
+This project is a fork of a subset of the WindowBuilder Eclipse
+plugin: http://www.eclipse.org/windowbuilder/
+
+Specifically, it contains the subset of WindowBuilder related to the
+propertysheet, intended for reuse in AOSP by the ADT plugin.
+
+The fork was modified as follows:
+* Started with revision 424 from the trunk:
+  http://dev.eclipse.org/svnroot/tools/org.eclipse.windowbuilder/trunk
+
+* Extracted the property package from org.eclipse.wb.core:
+   src/org/eclipse/wb/internal/core/model/property
+  and then everything it transitively references.  This turns out to
+  be a lot. I then started pruning out references to code we don't
+  need, such as support for editing Java constructs such as enums, or
+  dealing with a Java code model, etc.  This means some of the files
+  have been edited to remove methods and fields. For example, the
+  property category code was modified to no longer support the
+  persistent storage of categories.
+
+* The WindowBuilder code depended on a number of Apache Commons
+  libraries such as collections, lang, etc. Since ADT already uses
+  Guava, which provides a lot of the same functionality, I replaced
+  all the Commons calls with Guava calls in order to avoid having to
+  make ADT depend on (and load at runtime) the Commons libraries.
+
+* Finally, the propertysheet code was made into a library instead of a
+  plugin, such that it can be loaded into the ADT plugin.  This meant
+  mostly rewriting the DesignerPlugin class. It has kept its name
+  (since a lot of code references it for logging, resource loading
+  etc), but it is no longer an actual plugin. Instead it has init and
+  dispose methods for use by the AdtPlugin, and for logging it
+  delegates to the ADT plugin, etc.
+
+* Icons were moved into the DesignerPlugin package such that the
+  resource loading code could use a relative path, since with an
+  absolute path it would be looking in the embedding plugin's
+  resources.
+
+* To be consistent with the ADT codebase, I converted the files from
+  \r\n to \n newlines. Other than that, all formatting was left
+  unmodified.
+
+* Removed unused resources such as unreferences colors from
+  IColorConstants, unneeded messages from ModelMessages, and so on.
+
+* Note also that this Eclipse project is using a modified version of
+  the standard ADT Eclipse compiler settings: methods overriding other
+  methods and interfaces *without* using an @Override annotation are
+  ignored, since they were not using @Override annotations in the
+  WindowBuilder source base.
+  
+
+ADT ENHANCEMENTS
+------------------
+* I also modified the propertysheet in a few ways to add features
+  needed by ADT. These are all bracketed in the codebase with
+   // BEGIN ADT MODIFICATIONS
+   ...
+   // END ADT MODIFICATIONS
+
+  Specifically, I made the property table able to expand all and
+  collapse all. Properties have sorting priorities, and have separate
+  name and title attributes (and tooltips show the property name
+  rather than the title.) Text property editors allow field completion
+  by providing IContentProposalProvider (and optionally
+  ILabelProvider) instances via their getAdapter method. And the
+  property table will color values differently based on whether the
+  property is modified. (This allows us to draw default attributes
+  differently). Finally, the propertysheet now supports "expand by
+  default" (and for certain categories to be excluded, such as
+  deprecations).
+
+
+UPDATES
+--------
+
+We should keep an eye on the propertysheet code in WindowBuilder and
+migrate bug fixes and feature enhancements. To do that, first check
+out revision 424 from
+http://dev.eclipse.org/svnroot/tools/org.eclipse.windowbuilder/trunk
+That's the same baseline that this fork was based on.
+You can limit the checkout to just the org.eclipse.wb.core tree.
+
+Then check out the newest revision of WindowBuilder in a separate
+directory.
+
+Now diff the two trees. Look for diffs in the packages related to the
+propertysheet; this is going to be the packages that are present in
+this library.  If any of the diffs are related to the propertysheet or
+supporting code, apply them to this library, and then update this
+document to contain the new baseline revision (use 'svnversion .' to
+get the number).  Note that the diffs may need some rewriting if they
+reference Apache Commons code.
+
+Note that the ComponentsPropertiesPage.java class which is the main
+window in WindowBuilder is not used in our implementation; we instead
+have the PropertySheetPage class in ADT, so changes in that class
+should be checked to see whether they apply to our property sheet page
+(which uses the PropertyTable in a similar way, but obviously is based
+around our own UI model rather than the WindowBuilder ObjectInfo
+model.
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/CCombo3.java b/propertysheet/src/org/eclipse/wb/core/controls/CCombo3.java
new file mode 100644
index 0000000..8782e96
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/CCombo3.java
@@ -0,0 +1,510 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls;
+
+import org.eclipse.wb.draw2d.IColorConstants;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+import org.eclipse.wb.internal.core.utils.binding.editors.controls.DefaultControlActionsManager;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.TypedListener;
+import org.eclipse.swt.widgets.Widget;
+
+/**
+ * Combo control for {@link PropertyTable} and combo property editors.
+ * 
+ * @author scheglov_ke
+ * @coverage core.control
+ */
+public class CCombo3 extends Composite {
+  private final long m_createTime = System.currentTimeMillis();
+  private final CImageLabel m_text;
+  private final Button m_arrow;
+  private final Shell m_popup;
+  private final Table m_table;
+  private boolean m_fullDropdownTableSize = false;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public CCombo3(Composite parent, int style) {
+    super(parent, style);
+    addEvents(this, m_comboListener, new int[]{SWT.Dispose, SWT.Move, SWT.Resize});
+    // create label
+    {
+      m_text = new CImageLabel(this, SWT.NONE);
+      new DefaultControlActionsManager(m_text);
+      addEvents(m_text, m_textListener, new int[]{
+          SWT.KeyDown,
+          SWT.KeyUp,
+          SWT.MouseDown,
+          SWT.MouseUp,
+          SWT.MouseMove,
+          SWT.MouseDoubleClick,
+          SWT.Traverse,
+          SWT.FocusIn,
+          SWT.FocusOut});
+    }
+    // create arrow
+    {
+      m_arrow = new Button(this, SWT.ARROW | SWT.DOWN);
+      addEvents(m_arrow, m_arrowListener, new int[]{SWT.Selection, SWT.FocusIn, SWT.FocusOut});
+    }
+    // create popup Shell
+    {
+      Shell shell = getShell();
+      m_popup = new Shell(shell, SWT.NONE);
+      m_popup.setLayout(new FillLayout());
+    }
+    // create table for items
+    {
+      m_table = new Table(m_popup, SWT.FULL_SELECTION);
+      addEvents(m_table, m_tableListener, new int[]{SWT.Selection, SWT.FocusIn, SWT.FocusOut});
+      //
+      new TableColumn(m_table, SWT.NONE);
+    }
+    // Focus tracking filter
+    {
+      final Listener filter = new Listener() {
+        private boolean hasFocus;
+
+        public void handleEvent(Event event) {
+          boolean old_hasFocus = hasFocus;
+          hasFocus =
+              m_text.isFocusControl()
+                  || m_arrow.isFocusControl()
+                  || m_popup.isFocusControl()
+                  || m_table.isFocusControl();
+          // configure colors
+          if (hasFocus) {
+            m_text.setBackground(IColorConstants.listSelection);
+            m_text.setForeground(IColorConstants.listSelectionText);
+          } else {
+            m_text.setBackground(IColorConstants.listBackground);
+            m_text.setForeground(IColorConstants.listForeground);
+          }
+          // send FocusOut event
+          if (old_hasFocus && !hasFocus) {
+            Event e = new Event();
+            e.widget = CCombo3.this;
+            e.time = event.time;
+            notifyListeners(SWT.FocusOut, e);
+          }
+        }
+      };
+      getDisplay().addFilter(SWT.FocusIn, filter);
+      addListener(SWT.Dispose, new Listener() {
+        public void handleEvent(Event event) {
+          getDisplay().removeFilter(SWT.FocusIn, filter);
+        }
+      });
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Events handling
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private final Listener m_comboListener = new Listener() {
+    public void handleEvent(Event event) {
+      switch (event.type) {
+        case SWT.Dispose :
+          if (!m_popup.isDisposed()) {
+            m_popup.dispose();
+          }
+          break;
+        case SWT.Move :
+          doDropDown(false);
+          break;
+        case SWT.Resize :
+          doResize();
+          break;
+      }
+    }
+  };
+  private final Listener m_textListener = new Listener() {
+    public void handleEvent(final Event event) {
+      switch (event.type) {
+        case SWT.MouseDown :
+          if (System.currentTimeMillis() - m_createTime < 400) {
+            // send "logical" double click for case when we just activated combo
+            // and almost right away click second time (but first time on editor)
+            event.detail = -1;
+            notifyListeners(SWT.MouseDoubleClick, event);
+            // when we use "auto drop on editor activation" option, this click is
+            // is "logically" second one, so it should close combo
+            if (!isDisposed()) {
+              doDropDown(false);
+            }
+          } else {
+            m_text.setCapture(true);
+            doDropDown(!isDropped());
+          }
+          break;
+        case SWT.MouseUp : {
+          m_text.setCapture(false);
+          TableItem item = getItemUnderCursor(event);
+          if (item != null) {
+            doDropDown(false);
+            sendSelectionEvent(event);
+          }
+          break;
+        }
+        case SWT.MouseDoubleClick :
+          // prevent resending MouseDoubleClick that we sent on fast MouseDown
+          if (event.detail != -1) {
+            notifyListeners(SWT.MouseDoubleClick, event);
+          }
+          break;
+        case SWT.MouseMove : {
+          TableItem item = getItemUnderCursor(event);
+          if (item != null) {
+            m_table.setSelection(new TableItem[]{item});
+          }
+          break;
+        }
+        case SWT.KeyDown : {
+          // check for keyboard navigation and selection
+          {
+            int selectionIndex = m_table.getSelectionIndex();
+            if (event.keyCode == SWT.ARROW_UP) {
+              selectionIndex--;
+              if (selectionIndex < 0) {
+                selectionIndex = m_table.getItemCount() - 1;
+              }
+              m_table.setSelection(selectionIndex);
+              return;
+            } else if (event.keyCode == SWT.ARROW_DOWN) {
+              m_table.setSelection((selectionIndex + 1) % m_table.getItemCount());
+              return;
+            } else if (event.character == SWT.CR || event.character == ' ') {
+              sendSelectionEvent(event);
+              return;
+            }
+          }
+          // be default just resend event
+          resendKeyEvent(event);
+          break;
+        }
+        case SWT.KeyUp :
+          resendKeyEvent(event);
+          break;
+      }
+    }
+
+    private TableItem getItemUnderCursor(Event event) {
+      Point displayLocation = m_text.toDisplay(new Point(event.x, event.y));
+      Point tableLocation = m_table.toControl(displayLocation);
+      return m_table.getItem(tableLocation);
+    }
+  };
+  private final Listener m_arrowListener = new Listener() {
+    public void handleEvent(Event event) {
+      switch (event.type) {
+      /*case SWT.FocusIn : {
+       resendFocusEvent(event);
+       break;
+       }*/
+        case SWT.Selection : {
+          doDropDown(!isDropped());
+          break;
+        }
+      }
+    }
+  };
+  private final Listener m_tableListener = new Listener() {
+    public void handleEvent(Event event) {
+      switch (event.type) {
+        case SWT.Selection : {
+          doDropDown(false);
+          // show selected item in text
+          {
+            int index = m_table.getSelectionIndex();
+            select(index);
+          }
+          // send selection event
+          sendSelectionEvent(event);
+          break;
+        }
+      }
+    }
+  };
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Events utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sends selection event.
+   */
+  private void sendSelectionEvent(Event event) {
+    Event e = new Event();
+    e.time = event.time;
+    e.stateMask = event.stateMask;
+    notifyListeners(SWT.Selection, e);
+  }
+
+  /**
+   * Resends KeyDown/KeyUp events.
+   */
+  private void resendKeyEvent(Event event) {
+    Event e = new Event();
+    e.time = event.time;
+    e.character = event.character;
+    e.keyCode = event.keyCode;
+    e.stateMask = event.stateMask;
+    notifyListeners(event.type, e);
+  }
+
+  /**
+   * Adds given listener as handler for events in given widget.
+   */
+  private void addEvents(Widget widget, Listener listener, int[] events) {
+    for (int i = 0; i < events.length; i++) {
+      widget.addListener(events[i], listener);
+    }
+  }
+
+  /**
+   * Adds the listener to receive events.
+   */
+  public void addSelectionListener(SelectionListener listener) {
+    checkWidget();
+    if (listener == null) {
+      SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    }
+    TypedListener typedListener = new TypedListener(listener);
+    addListener(SWT.Selection, typedListener);
+    addListener(SWT.DefaultSelection, typedListener);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Activity
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets drop state of combo.
+   */
+  public void doDropDown(boolean drop) {
+    // check, may be we already in this drop state
+    if (drop == isDropped()) {
+      return;
+    }
+    // close combo
+    if (!drop) {
+      m_popup.setVisible(false);
+      m_text.setFocus();
+      return;
+    }
+    // open combo
+    {
+      // prepare popup location
+      Point comboSize = getSize();
+      Point popupLocation;
+      {
+        //popupLocation = getParent().toDisplay(getLocation());
+        popupLocation = toDisplay(new Point(0, 0));
+        popupLocation.y += comboSize.y;
+      }
+      // calculate and set popup location
+      {
+        TableColumn tableColumn = m_table.getColumn(0);
+        // pack everything
+        tableColumn.pack();
+        m_table.pack();
+        m_popup.pack();
+        // calculate bounds
+        Rectangle tableBounds = m_table.getBounds();
+        tableBounds.height = Math.min(tableBounds.height, m_table.getItemHeight() * 20); // max 20 items without scrolling
+        m_table.setBounds(tableBounds);
+        // calculate size
+        int remainingDisplayHeight = getDisplay().getClientArea().height - popupLocation.y - 10;
+        int preferredHeight = Math.min(tableBounds.height, remainingDisplayHeight);
+        int remainingDisplayWidth = getDisplay().getClientArea().width - popupLocation.x - 5;
+        int preferredWidth =
+            isFullDropdownTableWidth()
+                ? Math.min(tableBounds.width, remainingDisplayWidth)
+                : comboSize.x;
+        // set popup bounds calculated as computeTrim basing on combo width and table height paying attention on remaining display space
+        Rectangle popupBounds =
+            m_popup.computeTrim(popupLocation.x, popupLocation.y, preferredWidth, preferredHeight);
+        m_popup.setBounds(popupBounds);
+        // adjust column size
+        tableColumn.setWidth(m_table.getClientArea().width);
+      }
+      m_popup.setVisible(true);
+      // scroll to selection if needed
+      m_table.showSelection();
+    }
+  }
+
+  /**
+   * Initiates "press-hold-drag" sequence.
+   */
+  public void startDrag() {
+    m_text.setCapture(true);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public void setFullDropdownTableWidth(boolean freeTableSize) {
+    m_fullDropdownTableSize = freeTableSize;
+  }
+
+  public boolean isFullDropdownTableWidth() {
+    return m_fullDropdownTableSize;
+  }
+
+  public boolean isDropped() {
+    return m_popup.isVisible();
+  }
+
+  public void setQuickSearch(boolean value) {
+    // TODO
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access: items
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Removes all items.
+   */
+  public void removeAll() {
+    TableItem[] items = m_table.getItems();
+    for (int index = 0; index < items.length; index++) {
+      TableItem item = items[index];
+      item.dispose();
+    }
+  }
+
+  /**
+   * Adds new item with given text.
+   */
+  public void add(String text) {
+    add(text, null);
+  }
+
+  /**
+   * Adds new item with given text and image.
+   */
+  public void add(String text, Image image) {
+    checkWidget();
+    TableItem item = new TableItem(m_table, SWT.NONE);
+    item.setText(text);
+    item.setImage(image);
+  }
+
+  /**
+   * @return an item at given index
+   */
+  public String getItem(int index) {
+    checkWidget();
+    return m_table.getItem(index).getText();
+  }
+
+  /**
+   * @return the number of items
+   */
+  public int getItemCount() {
+    checkWidget();
+    return m_table.getItemCount();
+  }
+
+  /**
+   * @return the index of the selected item
+   */
+  public int getSelectionIndex() {
+    checkWidget();
+    return m_table.getSelectionIndex();
+  }
+
+  /**
+   * Selects an item with given index.
+   */
+  public void select(int index) {
+    checkWidget();
+    if (index == -1) {
+      m_table.deselectAll();
+      m_text.setText(null);
+      m_text.setImage(null);
+      return;
+    } else {
+      TableItem item = m_table.getItem(index);
+      m_text.setText(item.getText());
+      m_text.setImage(item.getImage());
+      m_table.select(index);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access: text and image
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Selects item with given text.
+   */
+  public void setText(String text) {
+    // try to find item with given text
+    TableItem[] items = m_table.getItems();
+    for (int index = 0; index < items.length; index++) {
+      TableItem item = items[index];
+      if (item.getText().equals(text)) {
+        select(index);
+        return;
+      }
+    }
+    // not found, remove selection
+    select(-1);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Resize support
+  // TODO: computeSize
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected void doResize() {
+    Rectangle clientArea = getClientArea();
+    int areaWidth = clientArea.width;
+    int areaHeight = clientArea.height;
+    // compute sizes of controls
+    Point buttonSize = m_arrow.computeSize(areaHeight, areaHeight);
+    Point textSize = m_text.computeSize(areaWidth - buttonSize.x, areaHeight);
+    // set controls location/size
+    m_arrow.setLocation(areaWidth - buttonSize.x, 0);
+    m_arrow.setSize(buttonSize);
+    m_text.setSize(areaWidth - buttonSize.x, Math.max(textSize.y, areaHeight));
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/CComboBox.java b/propertysheet/src/org/eclipse/wb/core/controls/CComboBox.java
new file mode 100644
index 0000000..9f0c8f9
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/CComboBox.java
@@ -0,0 +1,664 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls;
+
+import com.google.common.collect.Lists;
+
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TypedEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.TypedListener;
+import org.eclipse.wb.internal.core.model.property.editor.TextControlActionsManager;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+import org.eclipse.wb.internal.core.utils.check.Assert;
+
+import java.util.ArrayList;
+
+/**
+ * Extended ComboBox control for {@link PropertyTable} and combo property editors.
+ *
+ * @author sablin_aa
+ * @coverage core.control
+ */
+public class CComboBox extends Composite {
+  private Text m_text;
+  private Button m_button;
+  private Canvas m_canvas;
+  private Shell m_popup;
+  private TableViewer m_table;
+  private boolean m_fullDropdownTableWidth = false;
+  private boolean m_wasFocused;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public CComboBox(Composite parent, int style) {
+    super(parent, style);
+    createContents(this);
+    m_wasFocused = isComboFocused();
+    // add display hook
+    final Listener displayFocusInHook = new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        boolean focused = isComboFocused();
+        if (m_wasFocused && !focused) {
+          // close DropDown on focus out ComboBox
+          comboDropDown(false);
+        }
+        if (event.widget != CComboBox.this) {
+          // forward to ComboBox listeners
+          if (!m_wasFocused && focused) {
+            event.widget = CComboBox.this;
+            notifyListeners(SWT.FocusIn, event);
+          }
+          if (m_wasFocused && !focused) {
+            event.widget = CComboBox.this;
+            notifyListeners(SWT.FocusOut, event);
+          }
+        }
+        m_wasFocused = focused;
+      }
+    };
+    final Listener displayFocusOutHook = new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        m_wasFocused = isComboFocused();
+      }
+    };
+    {
+      Display display = getDisplay();
+      display.addFilter(SWT.FocusIn, displayFocusInHook);
+      display.addFilter(SWT.FocusOut, displayFocusOutHook);
+    }
+    // combo listeners
+    addControlListener(new ControlAdapter() {
+      @Override
+      public void controlResized(ControlEvent e) {
+        resizeInner();
+      }
+    });
+    addDisposeListener(new DisposeListener() {
+      @Override
+    public void widgetDisposed(DisposeEvent e) {
+        {
+          // remove Display hooks
+          Display display = getDisplay();
+          display.removeFilter(SWT.FocusIn, displayFocusInHook);
+          display.removeFilter(SWT.FocusOut, displayFocusOutHook);
+        }
+        disposeInner();
+      }
+    });
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Contents
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected void createContents(Composite parent) {
+    createText(parent);
+    createButton(parent);
+    createImage(parent);
+    createPopup(parent);
+  }
+
+  /**
+   * Create Text widget.
+   */
+  protected void createText(Composite parent) {
+    m_text = new Text(parent, SWT.NONE);
+    new TextControlActionsManager(m_text);
+    // key press processing
+    m_text.addKeyListener(new KeyAdapter() {
+      @Override
+      public void keyPressed(KeyEvent e) {
+        switch (e.keyCode) {
+          case SWT.ESC :
+            if (isDroppedDown()) {
+              // close dropdown
+              comboDropDown(false);
+              e.doit = false;
+            } else {
+              // forward to ComboBox listeners
+              notifyListeners(SWT.KeyDown, convert2event(e));
+            }
+            break;
+          case SWT.ARROW_UP :
+            if (isDroppedDown()) {
+              // prev item in dropdown list
+              Table table = m_table.getTable();
+              int index = table.getSelectionIndex() - 1;
+              table.setSelection(index < 0 ? table.getItemCount() - 1 : index);
+              e.doit = false;
+            } else {
+              // forward to ComboBox listeners
+              notifyListeners(SWT.KeyDown, convert2event(e));
+            }
+            break;
+          case SWT.ARROW_DOWN :
+            if (isDroppedDown()) {
+              // next item in dropdown list
+              Table table = m_table.getTable();
+              int index = table.getSelectionIndex() + 1;
+              table.setSelection(index == table.getItemCount() ? 0 : index);
+              e.doit = false;
+            } else if ((e.stateMask & SWT.ALT) != 0) {
+              // force drop down combo
+              comboDropDown(true);
+              e.doit = false;
+              // return focus to text
+              setFocus2Text(false);
+            } else {
+              // forward to ComboBox listeners
+              notifyListeners(SWT.KeyDown, convert2event(e));
+            }
+            break;
+          case '\r' :
+            Table table = m_table.getTable();
+            if (isDroppedDown() && table.getSelectionIndex() != -1) {
+              // forward to Table listeners
+              table.notifyListeners(SWT.Selection, convert2event(e));
+            } else {
+              m_text.selectAll();
+              setSelectionText(getEditText());
+              // forward to ComboBox listeners
+              notifyListeners(SWT.Selection, convert2event(e));
+            }
+            break;
+        }
+      }
+    });
+    // modifications processing
+    m_text.addModifyListener(new ModifyListener() {
+      @Override
+    public void modifyText(ModifyEvent e) {
+        if (isDroppedDown()) {
+          m_table.refresh();
+        } else {
+          // force drop down combo
+          if (m_text.isFocusControl()) {
+            comboDropDown(true);
+            // return focus to text
+            setFocus2Text(false);
+          }
+        }
+      }
+    });
+  }
+
+  /**
+   * Create arrow button.
+   */
+  protected void createButton(Composite parent) {
+    m_button = new Button(parent, SWT.ARROW | SWT.DOWN);
+    m_button.addSelectionListener(new SelectionAdapter() {
+      @Override
+      public void widgetSelected(SelectionEvent e) {
+        comboDropDown(!isDroppedDown());
+        // return focus to text
+        setFocus2Text(true);
+      }
+    });
+  }
+
+  /**
+   * Create image canvas.
+   */
+  protected void createImage(Composite parent) {
+    m_canvas = new Canvas(parent, SWT.BORDER);
+    m_canvas.addPaintListener(new PaintListener() {
+      @Override
+    public void paintControl(PaintEvent e) {
+        Image selectionImage = getSelectionImage();
+        if (selectionImage != null) {
+          e.gc.drawImage(selectionImage, 0, 0);
+        } else {
+          e.gc.fillRectangle(m_canvas.getClientArea());
+        }
+      }
+    });
+  }
+
+  /**
+   * Create popup shell with table.
+   */
+  protected void createPopup(Composite parent) {
+    m_popup = new Shell(getShell(), SWT.BORDER);
+    m_popup.setLayout(new FillLayout());
+    createTable(m_popup);
+  }
+
+  /**
+   * Create table.
+   */
+  protected void createTable(Composite parent) {
+    m_table = new TableViewer(parent, SWT.FULL_SELECTION);
+    new TableViewerColumn(m_table, SWT.LEFT);
+    m_table.getTable().addSelectionListener(new SelectionAdapter() {
+      @Override
+      public void widgetSelected(SelectionEvent e) {
+        int selectionIndex = m_table.getTable().getSelectionIndex();
+        setSelectionIndex(selectionIndex);
+        comboDropDown(false);
+        // forward to ComboBox listeners
+        notifyListeners(SWT.Selection, convert2event(e));
+      }
+    });
+    m_table.setContentProvider(getContentProvider());
+    m_table.setLabelProvider(getLabelProvider());
+    m_table.addFilter(getFilterProvider());
+  }
+
+  /**
+   * Placement inner widgets.
+   */
+  protected void resizeInner() {
+    Rectangle clientArea = getClientArea();
+    int rightOccupied = 0;
+    int leftOccupied = 0;
+    {
+      // button
+      m_button.setBounds(
+          clientArea.width - clientArea.height,
+          0,
+          clientArea.height,
+          clientArea.height);
+      rightOccupied = clientArea.height;
+    }
+    {
+      Image selectionImage = getSelectionImage();
+      if (selectionImage != null) {
+        // image
+        m_canvas.setSize(clientArea.height, clientArea.height);
+        leftOccupied = clientArea.height;
+      } else {
+        m_canvas.setSize(1, clientArea.height);
+        leftOccupied = 1;
+      }
+    }
+    {
+      // text
+      m_text.setBounds(
+          leftOccupied,
+          0,
+          clientArea.width - rightOccupied - leftOccupied,
+          clientArea.height);
+    }
+  }
+
+  /**
+   * Dispose inner widgets.
+   */
+  protected void disposeInner() {
+    if (!m_popup.isDisposed()) {
+      m_popup.dispose();
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Providers
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected IContentProvider getContentProvider() {
+    return new IStructuredContentProvider() {
+      @Override
+    public Object[] getElements(Object inputElement) {
+        return m_items.toArray(new ComboBoxItem[m_items.size()]);
+      }
+
+      @Override
+    public void dispose() {
+      }
+
+      @Override
+    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+      }
+    };
+  }
+
+  protected IBaseLabelProvider getLabelProvider() {
+    return new LabelProvider() {
+      @Override
+      public Image getImage(Object element) {
+        ComboBoxItem item = (ComboBoxItem) element;
+        return item.m_image;
+      }
+
+      @Override
+      public String getText(Object element) {
+        ComboBoxItem item = (ComboBoxItem) element;
+        return item.m_label;
+      }
+    };
+  }
+
+  protected ViewerFilter getFilterProvider() {
+    return new ViewerFilter() {
+      @Override
+      public boolean select(Viewer viewer, Object parentElement, Object element) {
+        String lookingString = m_text.getText().toLowerCase();
+        if (isDroppedDown() && lookingString.length() > 0) {
+          ComboBoxItem item = (ComboBoxItem) element;
+          return item.m_label.toLowerCase().indexOf(lookingString) != -1;
+        }
+        return true;
+      }
+    };
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Items
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected static class ComboBoxItem {
+    public final String m_label;
+    public final Image m_image;
+
+    public ComboBoxItem(String label, Image image) {
+      m_label = label;
+      m_image = image;
+    }
+  }
+
+  ArrayList<ComboBoxItem> m_items = Lists.newArrayList();
+
+  /**
+   * Add new item.
+   */
+  public void addItem(String label, Image image) {
+    Assert.isTrue(!isDroppedDown());
+    m_items.add(new ComboBoxItem(label, image));
+  }
+
+  public void addItem(String label) {
+    addItem(label, null);
+  }
+
+  public void removeAll() {
+    m_items.clear();
+  }
+
+  public int getItemCount() {
+    return m_items.size();
+  }
+
+  public String getItemLabel(int index) {
+    return m_items.get(index).m_label;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public boolean isComboFocused() {
+    return isFocusControl()
+        || m_text.isFocusControl()
+        || m_button.isFocusControl()
+        || m_canvas.isFocusControl()
+        || m_popup.isFocusControl()
+        || m_table.getTable().isFocusControl();
+  }
+
+  /**
+   * Edit text.
+   */
+  public String getEditText() {
+    return m_text.getText();
+  }
+
+  public void setEditText(String text) {
+    m_text.setText(text == null ? "" : text);
+    m_text.selectAll();
+  }
+
+  public void setEditSelection(int start, int end) {
+    m_text.setSelection(start, end);
+  }
+
+  /**
+   * Read only.
+   */
+  public void setReadOnly(boolean value) {
+    m_text.setEditable(!value);
+    m_button.setEnabled(!value);
+  }
+
+  /**
+   * Drop down width.
+   */
+  public boolean isFullDropdownTableWidth() {
+    return m_fullDropdownTableWidth;
+  }
+
+  public void setFullDropdownTableWidth(boolean value) {
+    Assert.isTrue(!isDroppedDown());
+    m_fullDropdownTableWidth = value;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Selection
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private int m_selectionIndex = -1;
+
+  /**
+   * Selection index.
+   */
+  public int getSelectionIndex() {
+    return m_selectionIndex;
+  }
+
+  public void setSelectionIndex(int index) {
+    m_selectionIndex = index;
+    if (isDroppedDown()) {
+      m_table.getTable().setSelection(m_selectionIndex);
+    }
+    setEditText(getSelectionText());
+  }
+
+  /**
+   * Selection text.
+   */
+  private String getSelectionText() {
+    if (m_selectionIndex != -1 && isDroppedDown()) {
+      Object itemData = m_table.getTable().getItem(m_selectionIndex).getData();
+      return ((ComboBoxItem) itemData).m_label;
+    }
+    return null;
+  }
+
+  /**
+   * Selection image.
+   */
+  private Image getSelectionImage() {
+    return m_selectionIndex != -1 ? m_items.get(m_selectionIndex).m_image : null;
+  }
+
+  public void setSelectionText(String label) {
+    TableItem[] items = m_table.getTable().getItems();
+    for (int i = 0; i < items.length; i++) {
+      TableItem item = items[i];
+      if (item.getText().equals(label)) {
+        setSelectionIndex(i);
+        return;
+      }
+    }
+    // no such item
+    setSelectionIndex(-1);
+    setEditText(label);
+  }
+
+  /**
+   * Adds the listener to receive events.
+   */
+  public void addSelectionListener(SelectionListener listener) {
+    checkWidget();
+    if (listener == null) {
+      SWT.error(SWT.ERROR_NULL_ARGUMENT);
+    }
+    TypedListener typedListener = new TypedListener(listener);
+    addListener(SWT.Selection, typedListener);
+    addListener(SWT.DefaultSelection, typedListener);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Popup
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public boolean isDroppedDown() {
+    return m_popup.isVisible();
+  }
+
+  public void comboDropDown(boolean dropdown) {
+    // check, may be we already in this drop state
+    if (dropdown == isDroppedDown()) {
+      return;
+    }
+    // close combo
+    if (dropdown) {
+      // initialize
+      m_table.setInput(m_items);
+      Table table = m_table.getTable();
+      TableColumn column = table.getColumn(0);
+      column.pack();
+      table.pack();
+      m_popup.pack();
+      // compute table size
+      Rectangle tableBounds = table.getBounds();
+      tableBounds.height = Math.min(tableBounds.height, table.getItemHeight() * 15);// max 15 items without scrolling
+      table.setBounds(tableBounds);
+      // prepare popup point
+      Point comboLocation = toDisplay(new Point(0, 0));
+      Point comboSize = getSize();
+      // compute popup size
+      Display display = getDisplay();
+      Rectangle clientArea = display.getClientArea();
+      int remainingDisplayHeight = clientArea.height - comboLocation.y - comboSize.y - 10;
+      int preferredHeight = Math.min(tableBounds.height, remainingDisplayHeight);
+      int remainingDisplayWidth = clientArea.width - comboLocation.x - 10;
+      int preferredWidth =
+          isFullDropdownTableWidth()
+              ? Math.min(tableBounds.width, remainingDisplayWidth)
+              : comboSize.x;
+      Rectangle popupBounds =
+          new Rectangle(comboLocation.x,
+              comboLocation.y + comboSize.y,
+              preferredWidth,
+              preferredHeight);
+      Rectangle trimBounds =
+          m_popup.computeTrim(popupBounds.x, popupBounds.y, popupBounds.width, popupBounds.height);
+      m_popup.setBounds(popupBounds.x, popupBounds.y, 2 * popupBounds.width - trimBounds.width, 2
+          * popupBounds.height
+          - trimBounds.height);
+      // adjust column size
+      column.setWidth(table.getClientArea().width);
+      // show popup
+      m_popup.setVisible(true);
+      table.setSelection(getSelectionIndex());
+    } else {
+      // hide popup
+      m_popup.setVisible(false);
+    }
+  }
+
+  protected final void setFocus2Text(final boolean selectAll) {
+    getDisplay().asyncExec(new Runnable() {
+      final boolean m_selectAll = selectAll;
+
+      @Override
+    public void run() {
+        if (!m_text.isDisposed()) {
+          m_text.setFocus();
+          if (m_selectAll) {
+            m_text.selectAll();
+          }
+        }
+      }
+    });
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Utilities
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected static Event convert2event(TypedEvent tEvent) {
+    Event event = new Event();
+    event.widget = tEvent.widget;
+    event.display = tEvent.display;
+    event.widget = tEvent.widget;
+    event.time = tEvent.time;
+    event.data = tEvent.data;
+    if (tEvent instanceof KeyEvent) {
+      KeyEvent kEvent = (KeyEvent) tEvent;
+      event.character = kEvent.character;
+      event.keyCode = kEvent.keyCode;
+      event.stateMask = kEvent.stateMask;
+      event.doit = kEvent.doit;
+    }
+    if (tEvent instanceof SelectionEvent) {
+      SelectionEvent sEvent = (SelectionEvent) tEvent;
+      event.item = sEvent.item;
+      event.x = sEvent.x;
+      event.y = sEvent.y;
+      event.width = sEvent.width;
+      event.height = sEvent.height;
+      event.detail = sEvent.detail;
+      event.stateMask = sEvent.stateMask;
+      event.text = sEvent.text;
+      event.doit = sEvent.doit;
+    }
+    return event;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/CFlatButton.java b/propertysheet/src/org/eclipse/wb/core/controls/CFlatButton.java
new file mode 100644
index 0000000..156cf5e
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/CFlatButton.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls;
+
+import org.eclipse.wb.draw2d.IColorConstants;
+import org.eclipse.wb.internal.core.utils.ui.DrawUtils;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+/**
+ * Class representing flat push button as it looks in Mac OSX.
+ * 
+ * It doesn't draw text, not need for now. ;-)
+ * 
+ * @author mitin_aa
+ */
+public final class CFlatButton extends Canvas {
+  // colors
+  private static final Color COLOR_FACE = DrawUtils.getShiftedColor(IColorConstants.button, 12);
+  private static final Color COLOR_FACE_SELECTED = IColorConstants.buttonDarker;
+  private static final Color COLOR_BORDER_GRADIENT1 = DrawUtils.getShiftedColor(
+      IColorConstants.button,
+      -12);
+  private static final Color COLOR_BORDER_GRADIENT1_SELECTED = DrawUtils.getShiftedColor(
+      IColorConstants.buttonDarker,
+      64);
+  private static final Color COLOR_BORDER_GRADIENT2 = DrawUtils.getShiftedColor(COLOR_FACE, -8);
+  private static final Color COLOR_BORDER_GRADIENT2_SELECTED = DrawUtils.getShiftedColor(
+      COLOR_FACE_SELECTED,
+      -8);
+  // fields
+  private Image m_image;
+  private boolean m_down;
+  private boolean m_selected;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public CFlatButton(Composite parent, int style) {
+    super(parent, style);
+    addPaintListener(new PaintListener() {
+      public void paintControl(PaintEvent e) {
+        boolean isSelected = m_down | m_selected;
+        Color faceColor = isSelected ? COLOR_FACE_SELECTED : COLOR_FACE;
+        Color borderGradientColor1 =
+            isSelected ? COLOR_BORDER_GRADIENT1_SELECTED : COLOR_BORDER_GRADIENT1;
+        Color borderGradientColor2 =
+            isSelected ? COLOR_BORDER_GRADIENT2_SELECTED : COLOR_BORDER_GRADIENT2;
+        GC gc = e.gc;
+        Rectangle ca = getClientArea();
+        // draw client area
+        // dark border
+        gc.setForeground(IColorConstants.buttonDarker);
+        gc.drawRectangle(ca.x, ca.y, ca.width - 1, ca.height - 1);
+        cropClientArea(ca);
+        // gradient border
+        gc.setForeground(borderGradientColor1);
+        gc.setBackground(borderGradientColor2);
+        gc.fillGradientRectangle(ca.x, ca.y, ca.width, ca.height, true);
+        cropClientArea(ca);
+        // fill background
+        gc.setBackground(faceColor);
+        gc.fillRectangle(ca);
+        // draw face upper-half gradient 
+        Rectangle ca1 = getClientArea();
+        cropClientArea(ca1);
+        gc.setForeground(faceColor);
+        gc.setBackground(borderGradientColor1);
+        gc.fillGradientRectangle(ca1.x, ca1.y, ca1.width, ca1.height / 4, true);
+        // draw face down-half gradient 
+        ca1.x += 1;
+        ca1.width -= 2;
+        gc.setForeground(borderGradientColor1);
+        gc.setBackground(faceColor);
+        gc.fillGradientRectangle(ca1.x, ca1.y + ca1.height / 4 - 1, ca1.width, ca1.height / 2, true);
+        // draw image
+        Image image = getImage();
+        if (image != null) {
+          Rectangle imageBounds = image.getBounds();
+          // center it in client area
+          int x = ca.x + (ca.width - imageBounds.width) / 2;
+          int y = ca.y + (ca.height - imageBounds.height) / 2;
+          gc.drawImage(image, x, y);
+        }
+      }
+    });
+    addListener(SWT.MouseDown, new Listener() {
+      public void handleEvent(Event e) {
+        m_down = true;
+        redraw();
+      }
+    });
+    addListener(SWT.MouseUp, new Listener() {
+      public void handleEvent(Event e) {
+        m_down = false;
+        redraw();
+        update();
+        if (getClientArea().contains(e.x, e.y)) {
+          fireSelectionEvent(e.time, e.stateMask);
+        }
+      }
+    });
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private void fireSelectionEvent(int time, int stateMask) {
+    Event event = new Event();
+    event.time = time;
+    event.stateMask = stateMask;
+    notifyListeners(SWT.Selection, event);
+  }
+
+  private void cropClientArea(Rectangle ca) {
+    ca.x += 1;
+    ca.y += 1;
+    ca.width -= 2;
+    ca.height -= 2;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public final Image getImage() {
+    return m_image;
+  }
+
+  public void setImage(Image image) {
+    m_image = image;
+  }
+
+  public void setSelected(boolean selected) {
+    m_selected = selected;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/CImageLabel.java b/propertysheet/src/org/eclipse/wb/core/controls/CImageLabel.java
new file mode 100644
index 0000000..eb5bce4
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/CImageLabel.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+/**
+ * Simple control for displaying image and text.
+ * 
+ * For unknown reason CLabel shows such things not very good - vertical text alignment is strange
+ * (bottom?).
+ * 
+ * @author scheglov_ke
+ * @coverage core.control
+ */
+public class CImageLabel extends Canvas {
+  private static final int SPACE = 5;
+  private Image m_image;
+  private String m_text;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public CImageLabel(Composite parent, int style) {
+    super(parent, style | SWT.NO_BACKGROUND);
+    addListener(SWT.Dispose, new Listener() {
+      public void handleEvent(Event event) {
+        if (m_backImage != null) {
+          m_backImage.dispose();
+          m_backImage = null;
+        }
+      }
+    });
+    addListener(SWT.Paint, new Listener() {
+      public void handleEvent(Event event) {
+        doPaint(event.gc);
+      }
+    });
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public Image getImage() {
+    return m_image;
+  }
+
+  public void setImage(Image image) {
+    m_image = image;
+    redraw();
+  }
+
+  public String getText() {
+    return m_text;
+  }
+
+  public void setText(String text) {
+    m_text = text;
+    redraw();
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Paint
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private Image m_backImage;
+
+  private void doPaint(GC paintGC) {
+    Rectangle clientArea = getClientArea();
+    // prepare back image
+    GC gc;
+    {
+      if (m_backImage == null || !m_backImage.getBounds().equals(clientArea)) {
+        if (m_backImage != null) {
+          m_backImage.dispose();
+        }
+        m_backImage = new Image(getDisplay(), clientArea.width, clientArea.height);
+      }
+      //
+      gc = new GC(m_backImage);
+      gc.setBackground(paintGC.getBackground());
+      gc.setForeground(paintGC.getForeground());
+      gc.fillRectangle(clientArea);
+    }
+    //
+    Point textExtent = m_text == null ? new Point(0, 0) : gc.textExtent(m_text);
+    Rectangle imageBounds = m_image == null ? new Rectangle(0, 0, 0, 0) : m_image.getBounds();
+    //
+    if (m_image != null) {
+      int x = clientArea.x;
+      int y = clientArea.y + (clientArea.height - imageBounds.height) / 2;
+      gc.drawImage(m_image, x, y);
+    }
+    if (m_text != null) {
+      int x = clientArea.x + imageBounds.width + SPACE;
+      int y = clientArea.y + (clientArea.height - textExtent.y) / 2;
+      gc.drawText(m_text, x, y);
+    }
+    // flush back image
+    {
+      paintGC.drawImage(m_backImage, 0, 0);
+      gc.dispose();
+    }
+  }
+
+  @Override
+  public Point computeSize(int wHint, int hHint, boolean changed) {
+    // prepare text size
+    GC gc = new GC(this);
+    Point textExtent = m_text == null ? new Point(0, 0) : gc.textExtent(m_text);
+    gc.dispose();
+    // prepare image size
+    Rectangle imageBounds = m_image == null ? new Rectangle(0, 0, 0, 0) : m_image.getBounds();
+    // calculate control size
+    int width = imageBounds.width + SPACE + textExtent.x;
+    int height = Math.max(imageBounds.height, textExtent.y);
+    return new Point(width, height);
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/CSpinner.java b/propertysheet/src/org/eclipse/wb/core/controls/CSpinner.java
new file mode 100644
index 0000000..d414df9
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/CSpinner.java
@@ -0,0 +1,569 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusAdapter;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Layout;
+import org.eclipse.swt.widgets.Spinner;
+import org.eclipse.swt.widgets.Text;
+
+import java.text.DecimalFormat;
+import java.text.MessageFormat;
+import java.text.ParseException;
+
+/**
+ * Custom implementation of {@link Spinner}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.control
+ */
+public class CSpinner extends Composite {
+  private static final Color COLOR_VALID = Display.getCurrent().getSystemColor(
+      SWT.COLOR_LIST_BACKGROUND);
+  private static final Color COLOR_INVALID = new Color(null, 255, 230, 230);
+  private int m_minimum = 0;
+  private int m_maximum = 100;
+  private int m_increment = 1;
+  private int m_value = 0;
+  private int m_multiplier = 1;
+  private String m_formatPattern = "0";
+  private DecimalFormat m_format = new DecimalFormat(m_formatPattern);
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // GUI fields
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private final Button m_button;
+  private final Text m_text;
+  private final Spinner m_spinner;
+  private Composite win32Hack;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public CSpinner(Composite parent, int style) {
+    super(parent, style);
+    m_button = new Button(this, SWT.ARROW | SWT.DOWN);
+    {
+      int textStyle = SWT.SINGLE | SWT.RIGHT;
+      if (IS_OS_MAC_OSX_COCOA) {
+        textStyle |= SWT.BORDER;
+      }
+      m_text = new Text(this, textStyle);
+      m_text.setText("" + m_value);
+      m_text.addKeyListener(new KeyAdapter() {
+        @Override
+        public void keyPressed(KeyEvent e) {
+          if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) {
+            e.doit = false;
+            updateValue(e.keyCode);
+          }
+        }
+
+        @Override
+        public void keyReleased(KeyEvent e) {
+          try {
+            m_value = (int) (m_format.parse(m_text.getText()).doubleValue() * m_multiplier);
+            if (m_value < m_minimum || m_value > m_maximum) {
+              m_text.setBackground(COLOR_INVALID);
+              setState(MessageFormat.format(
+                  Messages.CSpinner_outOfRange,
+                  m_value,
+                  m_minimum,
+                  m_maximum));
+              notifySelectionListeners(false);
+            } else {
+              setState(null);
+              notifySelectionListeners(true);
+            }
+          } catch (ParseException ex) {
+            setState(MessageFormat.format(
+                Messages.CSpinner_canNotParse,
+                m_text.getText(),
+                m_formatPattern));
+            notifySelectionListeners(false);
+          }
+        }
+      });
+    }
+    if (!IS_OS_MAC_OSX) {
+      win32Hack = new Composite(this, SWT.NONE);
+      win32Hack.setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
+      win32Hack.moveAbove(null);
+      win32Hack.moveBelow(m_text);
+    }
+    {
+      m_spinner = new Spinner(this, SWT.VERTICAL);
+      m_spinner.setMinimum(0);
+      m_spinner.setMaximum(50);
+      m_spinner.setIncrement(1);
+      m_spinner.setPageIncrement(1);
+      m_spinner.setSelection(25);
+      m_spinner.addFocusListener(new FocusAdapter() {
+        @Override
+        public void focusGained(FocusEvent e) {
+          setFocus();
+        }
+      });
+      m_spinner.addSelectionListener(new SelectionAdapter() {
+        @Override
+        public void widgetSelected(SelectionEvent e) {
+          m_text.forceFocus();
+          if (m_spinner.getSelection() > 25) {
+            updateValue(SWT.ARROW_UP);
+          } else {
+            updateValue(SWT.ARROW_DOWN);
+          }
+          m_spinner.setSelection(25);
+        }
+      });
+      setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
+      if (IS_OS_WINDOWS_XP || IS_OS_WINDOWS_2003) {
+        setLayout(new WindowsXpLayout());
+      } else if (IS_OS_WINDOWS_VISTA || IS_OS_WINDOWS_7) {
+        setLayout(new WindowsVistaLayout());
+      } else if (IS_OS_LINUX) {
+        setLayout(new LinuxLayout());
+      } else if (IS_OS_MAC_OSX) {
+        if (IS_OS_MAC_OSX_COCOA) {
+          setLayout(new MacCocoaLayout());
+        } else {
+          setLayout(new MacLayout());
+        }
+      } else {
+        setLayout(new WindowsXpLayout());
+      }
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public void setEnabled(boolean enabled) {
+    super.setEnabled(enabled);
+    m_text.setEnabled(enabled);
+    m_spinner.setEnabled(enabled);
+  }
+
+  /**
+   * Sets the number of decimal places used by the receiver.
+   * <p>
+   * See {@link Spinner#setDigits(int)}.
+   */
+  public void setDigits(int digits) {
+    m_formatPattern = "0.";
+    m_multiplier = 1;
+    for (int i = 0; i < digits; i++) {
+      m_formatPattern += "0";
+      m_multiplier *= 10;
+    }
+    m_format = new DecimalFormat(m_formatPattern);
+    updateText();
+  }
+
+  /**
+   * Sets minimum and maximum using single invocation.
+   */
+  public void setRange(int minimum, int maximum) {
+    setMinimum(minimum);
+    setMaximum(maximum);
+  }
+
+  /**
+   * @return the minimum value that the receiver will allow.
+   */
+  public int getMinimum() {
+    return m_minimum;
+  }
+
+  /**
+   * Sets the minimum value that the receiver will allow.
+   */
+  public void setMinimum(int minimum) {
+    m_minimum = minimum;
+    setSelection(Math.max(m_value, m_minimum));
+  }
+
+  /**
+   * Sets the maximum value that the receiver will allow.
+   */
+  public void setMaximum(int maximum) {
+    m_maximum = maximum;
+    setSelection(Math.min(m_value, m_maximum));
+  }
+
+  /**
+   * Sets the amount that the receiver's value will be modified by when the up/down arrows are
+   * pressed to the argument, which must be at least one.
+   */
+  public void setIncrement(int increment) {
+    m_increment = increment;
+  }
+
+  /**
+   * Sets the <em>value</em>, which is the receiver's position, to the argument. If the argument is
+   * not within the range specified by minimum and maximum, it will be adjusted to fall within this
+   * range.
+   */
+  public void setSelection(int newValue) {
+    newValue = Math.min(Math.max(m_minimum, newValue), m_maximum);
+    if (newValue != m_value) {
+      m_value = newValue;
+      updateText();
+      // set valid state
+      setState(null);
+    }
+  }
+
+  private void updateText() {
+    String text = m_format.format((double) m_value / m_multiplier);
+    m_text.setText(text);
+    m_text.selectAll();
+  }
+
+  /**
+   * @return the <em>selection</em>, which is the receiver's position.
+   */
+  public int getSelection() {
+    return m_value;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Update
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Updates {@link #m_value} into given direction.
+   */
+  private void updateValue(int direction) {
+    // prepare new value
+    int newValue;
+    {
+      newValue = m_value;
+      if (direction == SWT.ARROW_UP) {
+        newValue += m_increment;
+      }
+      if (direction == SWT.ARROW_DOWN) {
+        newValue -= m_increment;
+      }
+    }
+    // update value
+    setSelection(newValue);
+    notifySelectionListeners(true);
+  }
+
+  /**
+   * Sets the valid/invalid state.
+   * 
+   * @param message
+   *          the message to show, or <code>null</code> if valid.
+   */
+  private void setState(String message) {
+    m_text.setToolTipText(message);
+    if (message == null) {
+      m_text.setBackground(COLOR_VALID);
+    } else {
+      m_text.setBackground(COLOR_INVALID);
+    }
+  }
+
+  /**
+   * Notifies {@link SWT#Selection} listeners with value and state.
+   */
+  private void notifySelectionListeners(boolean valid) {
+    Event event = new Event();
+    event.detail = m_value;
+    event.doit = valid;
+    notifyListeners(SWT.Selection, event);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Windows XP
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Implementation of {@link Layout} for Windows XP.
+   */
+  private class WindowsXpLayout extends Layout {
+    @Override
+    protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
+      Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+      size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width;
+      // add Text widget margin
+      size.y += 2;
+      // apply hints
+      if (wHint != SWT.DEFAULT) {
+        size.x = Math.min(size.x, wHint);
+      }
+      if (hHint != SWT.DEFAULT) {
+        size.y = Math.min(size.y, hHint);
+      }
+      // OK, final size
+      return size;
+    }
+
+    @Override
+    protected void layout(Composite composite, boolean flushCache) {
+      Rectangle cRect = composite.getClientArea();
+      if (cRect.isEmpty()) {
+        return;
+      }
+      // prepare size of Text
+      Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+      // prepare size of Spinner
+      Point sSize;
+      sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
+      sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height));
+      sSize.x = Math.min(sSize.x, cRect.width);
+      // prepare width of arrows part of Spinner
+      int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
+      // set bounds for Spinner and Text
+      m_spinner.setBounds(
+          cRect.x + cRect.width - sSize.x + 1,
+          cRect.y - 1,
+          sSize.x,
+          cRect.height + 2);
+      m_text.setBounds(cRect.x, cRect.y + 1, cRect.width - arrowWidth, tSize.y);
+      win32Hack.setBounds(cRect.x, cRect.y, cRect.width - arrowWidth, sSize.y);
+    }
+  }
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Windows Vista
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Implementation of {@link Layout} for Windows Vista.
+   */
+  private class WindowsVistaLayout extends Layout {
+    @Override
+    protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
+      Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+      size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width;
+      // add Text widget margin
+      size.y += 3;
+      // apply hints
+      if (wHint != SWT.DEFAULT) {
+        size.x = Math.min(size.x, wHint);
+      }
+      if (hHint != SWT.DEFAULT) {
+        size.y = Math.min(size.y, hHint);
+      }
+      // OK, final size
+      return size;
+    }
+
+    @Override
+    protected void layout(Composite composite, boolean flushCache) {
+      Rectangle cRect = composite.getClientArea();
+      if (cRect.isEmpty()) {
+        return;
+      }
+      // prepare size of Text
+      Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+      // prepare size of Spinner
+      Point sSize;
+      sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
+      sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height));
+      sSize.x = Math.min(sSize.x, cRect.width);
+      // prepare width of arrows part of Spinner
+      int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
+      // set bounds for Spinner and Text
+      m_spinner.setBounds(
+          cRect.x + cRect.width - sSize.x + 1,
+          cRect.y - 1,
+          sSize.x,
+          cRect.height + 2);
+      m_text.setBounds(cRect.x, cRect.y + 1, cRect.width - arrowWidth, tSize.y);
+      win32Hack.setBounds(cRect.x, cRect.y, cRect.width - arrowWidth, sSize.y);
+    }
+  }
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Linux
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Implementation of {@link Layout} for Linux.
+   */
+  private class LinuxLayout extends Layout {
+    @Override
+    protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
+      Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+      size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width;
+      // apply hints
+      if (wHint != SWT.DEFAULT) {
+        size.x = Math.min(size.x, wHint);
+      }
+      if (hHint != SWT.DEFAULT) {
+        size.y = Math.min(size.y, hHint);
+      }
+      // OK, final size
+      return size;
+    }
+
+    @Override
+    protected void layout(Composite composite, boolean flushCache) {
+      Rectangle cRect = composite.getClientArea();
+      if (cRect.isEmpty()) {
+        return;
+      }
+      // prepare size of Text
+      Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+      // prepare size of Spinner
+      Point sSize;
+      sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
+      sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height));
+      sSize.x = Math.min(sSize.x, cRect.width);
+      // prepare width of arrows part of Spinner
+      int arrowWidth;
+      {
+        m_spinner.setSize(sSize);
+        arrowWidth = sSize.x - m_spinner.getClientArea().width;
+      }
+      // set bounds for Spinner and Text
+      m_spinner.setBounds(cRect.x + cRect.width - sSize.x, cRect.y - 2, sSize.x, cRect.height + 4);
+      m_text.setBounds(cRect.x, cRect.y, cRect.width - arrowWidth, tSize.y);
+    }
+  }
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // MacOSX
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Implementation of {@link Layout} for MacOSX.
+   */
+  private class MacLayout extends Layout {
+    @Override
+    protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
+      Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+      size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width;
+      // add Text widget margin
+      size.y += 4;
+      // apply hints
+      if (wHint != SWT.DEFAULT) {
+        size.x = Math.min(size.x, wHint);
+      }
+      if (hHint != SWT.DEFAULT) {
+        size.y = Math.min(size.y, hHint);
+      }
+      // OK, final size
+      return size;
+    }
+
+    @Override
+    protected void layout(Composite composite, boolean flushCache) {
+      Rectangle cRect = composite.getClientArea();
+      if (cRect.isEmpty()) {
+        return;
+      }
+      // prepare size of Text
+      Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+      tSize.y += 4;
+      // prepare size of Spinner
+      Point sSize;
+      sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
+      sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height));
+      sSize.x = Math.min(sSize.x, cRect.width);
+      // prepare width of arrows part of Spinner
+      int arrowWidth = m_button.computeSize(-1, -1).x;
+      // set bounds for Spinner and Text
+      m_spinner.setBounds(cRect.x + cRect.width - sSize.x, cRect.y, sSize.x, cRect.height);
+      m_text.setBounds(cRect.x, cRect.y + 2, cRect.width - arrowWidth - 2, tSize.y);
+    }
+  }
+  /**
+   * Implementation of {@link Layout} for MacOSX Cocoa.
+   */
+  private class MacCocoaLayout extends Layout {
+    @Override
+    protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
+      Point textSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+      Point spinnerSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+      int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
+      int width = textSize.x + arrowWidth;
+      int height = Math.max(spinnerSize.y, textSize.y);
+      // apply hints
+      if (wHint != SWT.DEFAULT) {
+        width = Math.min(width, wHint);
+      }
+      if (hHint != SWT.DEFAULT) {
+        height = Math.min(height, hHint);
+      }
+      return new Point(width, height);
+    }
+
+    @Override
+    protected void layout(Composite composite, boolean flushCache) {
+      Rectangle clientArea = composite.getClientArea();
+      if (clientArea.isEmpty()) {
+        return;
+      }
+      // prepare size of Spinner
+      Point spinnerSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
+      // prepare width of arrows part of Spinner
+      int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
+      m_spinner.setBounds(clientArea.x + clientArea.width - arrowWidth - 1, clientArea.y
+          + clientArea.height
+          - spinnerSize.y, arrowWidth + 2, spinnerSize.y);
+      m_text.setBounds(
+          clientArea.x + 2,
+          clientArea.y + 2,
+          clientArea.width - arrowWidth - 5,
+          clientArea.y + clientArea.height - 4);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // System utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private static final String OS_NAME = System.getProperty("os.name");
+  private static final String OS_VERSION = System.getProperty("os.version");
+  private static final String WS_TYPE = SWT.getPlatform();
+  private static final boolean IS_OS_MAC_OSX = isOS("Mac OS X");
+  private static final boolean IS_OS_MAC_OSX_COCOA = IS_OS_MAC_OSX && "cocoa".equals(WS_TYPE);
+  private static final boolean IS_OS_LINUX = isOS("Linux") || isOS("LINUX");
+  private static final boolean IS_OS_WINDOWS_XP = isWindowsVersion("5.1");
+  private static final boolean IS_OS_WINDOWS_2003 = isWindowsVersion("5.2");
+  private static final boolean IS_OS_WINDOWS_VISTA = isWindowsVersion("6.0");
+  private static final boolean IS_OS_WINDOWS_7 = isWindowsVersion("6.1");
+
+  private static boolean isOS(String osName) {
+    return OS_NAME != null && OS_NAME.startsWith(osName);
+  }
+
+  private static boolean isWindowsVersion(String windowsVersion) {
+    return isOS("Windows") && OS_VERSION != null && OS_VERSION.startsWith(windowsVersion);
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/Messages.java b/propertysheet/src/org/eclipse/wb/core/controls/Messages.java
new file mode 100644
index 0000000..3d83ffc
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/Messages.java
@@ -0,0 +1,16 @@
+package org.eclipse.wb.core.controls;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+  private static final String BUNDLE_NAME = "org.eclipse.wb.core.controls.messages"; //$NON-NLS-1$
+  public static String CSpinner_canNotParse;
+  public static String CSpinner_outOfRange;
+  static {
+    // initialize resource bundle
+    NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+  }
+
+  private Messages() {
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/messages.properties b/propertysheet/src/org/eclipse/wb/core/controls/messages.properties
new file mode 100644
index 0000000..75a1ca0
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/messages.properties
@@ -0,0 +1,2 @@
+CSpinner_canNotParse=Text "{0}"does not satisfy pattern "{1}" 
+CSpinner_outOfRange=Value {0} is out of range [{1}, {2}]
diff --git a/propertysheet/src/org/eclipse/wb/draw2d/IColorConstants.java b/propertysheet/src/org/eclipse/wb/draw2d/IColorConstants.java
new file mode 100644
index 0000000..f12d94e
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/draw2d/IColorConstants.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.draw2d;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * A collection of color-related constants.
+ *
+ * @author lobas_av
+ * @coverage gef.draw2d
+ */
+public interface IColorConstants {
+  /**
+   * System color used to paint highlight shadow areas.
+   */
+  Color buttonLightest = Utils.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
+  /**
+   * System color used to paint background areas.
+   */
+  Color button = Utils.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+  /**
+   * System color used to paint normal shadow areas.
+   */
+  Color buttonDarker = Utils.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
+//  /**
+//   * System color used to paint dark shadow areas.
+//   */
+//  Color buttonDarkest = Utils.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW);
+  /**
+   * System color used to paint list background areas.
+   */
+  Color listBackground = Utils.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
+  /**
+   * System color used to paint list foreground areas.
+   */
+  Color listForeground = Utils.getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+  /**
+   * System color used to paint list selection area.
+   */
+  Color listSelection = Utils.getSystemColor(SWT.COLOR_LIST_SELECTION);
+  /**
+   * System color used to paint list selection text.
+   */
+  Color listSelectionText = Utils.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
+  /**
+   * System color used to paint tooltip text.
+   */
+  Color tooltipForeground = Utils.getSystemColor(SWT.COLOR_INFO_FOREGROUND);
+  /**
+   * System color used to paint tooltip background areas.
+   */
+  Color tooltipBackground = Utils.getSystemColor(SWT.COLOR_INFO_BACKGROUND);
+  /**
+   * Miscellaneous colors.
+   */
+  Color lightGray = new Color(null, 192, 192, 192);
+  Color gray = new Color(null, 128, 128, 128);
+  Color darkGray = new Color(null, 64, 64, 64);
+  Color lightBlue = new Color(null, 127, 127, 255);
+  Color darkBlue = new Color(null, 0, 0, 127);
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Internal helper.
+   */
+  public static class Utils {
+    /**
+     * Invokes {@link Display#getSystemColor(int)} in UI thread.
+     */
+    private static Color getSystemColor(final int id) {
+      final Color[] color = new Color[1];
+      final Display display = Display.getDefault();
+      display.syncExec(new Runnable() {
+        @Override
+        public void run() {
+          color[0] = display.getSystemColor(id);
+        }
+      });
+      return color[0];
+    }
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/draw2d/ICursorConstants.java b/propertysheet/src/org/eclipse/wb/draw2d/ICursorConstants.java
new file mode 100644
index 0000000..b05035d
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/draw2d/ICursorConstants.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.draw2d;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Cursor;
+
+/**
+ * A collection of cursors.
+ *
+ * @author lobas_av
+ * @coverage gef.draw2d
+ */
+public interface ICursorConstants {
+  /**
+   * System resize west-east cursor
+   */
+  Cursor SIZEWE = new Cursor(null, SWT.CURSOR_SIZEWE);
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/DesignerPlugin.java b/propertysheet/src/org/eclipse/wb/internal/core/DesignerPlugin.java
new file mode 100644
index 0000000..564a068
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/DesignerPlugin.java
@@ -0,0 +1,229 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.wb.internal.core;
+
+import com.google.common.collect.Maps;
+import com.google.common.io.CharStreams;
+import com.google.common.io.Closeables;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+
+import java.io.Closeable;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+/**
+ * The DesignerPlugin class is the "nexus" of the propertysheet. In WindowBuilder,
+ * it's the plugin activator, and contains a number of important utility methods, such
+ * as resource loading, logging, obtaining a display and shell, etc.
+ * <p>
+ * In the AOSP fork, most of the functionality has been ripped out, except for the
+ * above mentioned pieces, and this class is no longer a plugin. Instead, it *delegates*
+ * to the plugin which initializes it via the {@link #initialize} method for things
+ * like logging. For things like image loading, it has its own local code such that
+ * it can find its image resources locally instead of requiring the embedding plugin
+ * to copy the images into its own jar.
+ * <p>
+ * "DesignerPlugin" is not a very good name for this class since it is not a plugin,
+ * but it was left that way to avoid modifying all the various propertysheet classes;
+ * we'd like to keep those as unmodified as possible to make absorbing future
+ * WindowBuilder improvements as easy as possible.
+ */
+public class DesignerPlugin {
+    private static AbstractUIPlugin sPlugin;
+    private static String sPluginId;
+
+    /**
+     * Initialize the property sheet for use in the ADT plugin
+     *
+     * @param hostPlugin the plugin to embed the property sheet
+     * @param pluginId the id of the plugin to use in status messages etc
+     * @param isWindows whether we're running on Windows
+     * @param isMac whether we're running on Mac
+     * @param isLinux whether we're running on Linux
+     */
+    public static void initialize(AbstractUIPlugin hostPlugin, String pluginId,
+            boolean isWindows, boolean isMac, boolean isLinux) {
+        assert sPlugin == null; // Can only be used by one client in the same classloader
+        sPlugin = hostPlugin;
+        sPluginId = pluginId;
+        EnvironmentUtils.IS_WINDOWS = isWindows;
+        EnvironmentUtils.IS_MAC = isMac;
+        EnvironmentUtils.IS_LINUX = isLinux;
+    }
+
+    /**
+     * Dispose the propertysheet library: free up images from the cache, unregister the
+     * plugin reference etc.
+     */
+    public static void dispose() {
+        sPlugin = null;
+        for (Image image : sImageCache.values()) {
+            image.dispose();
+        }
+        sImageCache.clear();
+        sDescriptorCache.clear();
+    }
+
+    /**
+     * Reads the contents of an {@link InputStreamReader} using the default
+     * platform encoding and return it as a String. This method will close the
+     * input stream.
+     *
+     * @param inputStream the input stream to be read from
+     * @param charset the charset to use
+     * @return the String read from the stream, or null if there was an error
+     */
+    public static String readFile(InputStream inputStream, Charset charset) {
+        if (inputStream == null) {
+            return null;
+        }
+        Closeable closeMe = inputStream;
+        try {
+            final InputStreamReader isr = new InputStreamReader(inputStream, charset);
+            closeMe = isr;
+            try {
+                return CharStreams.toString(isr);
+            } catch (Exception ioe) {
+                // pass -- ignore files we can't read
+                return null;
+            }
+        } finally {
+            Closeables.closeQuietly(closeMe);
+        }
+    }
+
+    /**
+     * @return the instance of {@link DesignerPlugin}
+     */
+    public static AbstractUIPlugin getDefault() {
+        assert sPlugin != null;
+        return sPlugin;
+    }
+
+    // //////////////////////////////////////////////////////////////////////////
+    //
+    // Display/Shell
+    //
+    // //////////////////////////////////////////////////////////////////////////
+    /**
+     * @return the {@link Display} instance, current (if in GUI thread) or
+     *         default.
+     */
+    public static Display getStandardDisplay() {
+        Display display = Display.getCurrent();
+        if (display == null) {
+            display = Display.getDefault();
+        }
+        return display;
+    }
+
+    /**
+     * @return the active {@link IWorkbenchWindow}.
+     */
+    public static IWorkbenchWindow getActiveWorkbenchWindow() {
+        return getDefault().getWorkbench().getActiveWorkbenchWindow();
+    }
+
+    /**
+     * @return the {@link Shell} of active {@link IWorkbenchWindow}.
+     */
+    public static Shell getShell() {
+        if (getActiveWorkbenchWindow() != null) {
+            return getActiveWorkbenchWindow().getShell();
+        }
+        return null;
+    }
+
+    /**
+     * Logs given {@link IStatus} into Eclipse .log.
+     */
+    public static void log(IStatus status) {
+        getDefault().getLog().log(status);
+    }
+
+    /**
+     * Logs {@link IStatus} with given message into Eclipse .log.
+     */
+    public static void log(String message) {
+        log(new Status(IStatus.INFO, sPluginId, IStatus.INFO, message, null));
+    }
+
+    /**
+     * Logs {@link IStatus} with given exception into Eclipse .log.
+     */
+    public static void log(Throwable e) {
+        Status status = new Status(IStatus.ERROR, sPluginId, "", e);
+        getDefault().getLog().log(status);
+    }
+
+    /**
+     * Logs {@link IStatus} with given message and exception into Eclipse .log.
+     */
+    public static void log(String message, Throwable e) {
+        log(createStatus(message, e));
+    }
+
+    /**
+     * Creates {@link IStatus} for given message and exception.
+     */
+    public static Status createStatus(String message, Throwable e) {
+        return new Status(IStatus.ERROR, "wb", IStatus.ERROR, message, e) {
+            @Override
+            public boolean isMultiStatus() {
+                return true;
+            }
+        };
+    }
+
+    // //////////////////////////////////////////////////////////////////////////
+    //
+    // Resources
+    //
+    // //////////////////////////////////////////////////////////////////////////
+    private static Map<String, ImageDescriptor> sDescriptorCache = Maps.newHashMap();
+    private static Map<String, Image> sImageCache = Maps.newHashMap();
+
+    public static Image getImage(String path) {
+        Image image = sImageCache.get(path);
+        if (image == null) {
+            ImageDescriptor descriptor = getImageDescriptor(path);
+            if (descriptor != null) {
+                return descriptor.createImage();
+            }
+            sImageCache.put(path, image);
+        }
+        return image;
+    }
+
+    public static ImageDescriptor getImageDescriptor(String path) {
+        ImageDescriptor descriptor = sDescriptorCache.get(path);
+        if (descriptor == null) {
+            URL url = DesignerPlugin.class.getResource("icons/" + path); //$NON-NLS-1$
+            if (url != null) {
+                descriptor = ImageDescriptor.createFromURL(url);
+                sDescriptorCache.put(path, descriptor);
+            }
+        }
+        return descriptor;
+    }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/EnvironmentUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/EnvironmentUtils.java
new file mode 100644
index 0000000..cedbbc0
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/EnvironmentUtils.java
@@ -0,0 +1,33 @@
+/*******************************************************************************

+ * Copyright (c) 2011 Google, Inc.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *    Google, Inc. - initial API and implementation

+ *******************************************************************************/

+package org.eclipse.wb.internal.core;

+

+import org.eclipse.ui.plugin.AbstractUIPlugin;

+

+/**

+ * Helper for environment state access.

+ *

+ * @author scheglov_ke

+ * @coverage core

+ */

+public final class EnvironmentUtils extends AbstractUIPlugin {

+  ////////////////////////////////////////////////////////////////////////////

+  //

+  // Operating systems

+  //

+  ////////////////////////////////////////////////////////////////////////////

+  /** True if this is running on Windows */

+  public static boolean IS_WINDOWS;

+  /** True if this is running on Mac */

+  public static boolean IS_MAC;

+  /** True if this is running on Linux */

+  public static boolean IS_LINUX;

+}

diff --git a/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/property/PropertyListIntersector.java b/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/property/PropertyListIntersector.java
new file mode 100644
index 0000000..24aea2f
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/property/PropertyListIntersector.java
@@ -0,0 +1,136 @@
+/*******************************************************************************

+ * Copyright (c) 2011 Google, Inc.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *    Google, Inc. - initial API and implementation

+ *******************************************************************************/

+package org.eclipse.wb.internal.core.editor.structure.property;

+

+import com.google.common.collect.Lists;

+

+import org.eclipse.wb.internal.core.model.property.Property;

+import org.eclipse.wb.internal.core.model.property.PropertyManager;

+

+import java.util.Iterator;

+import java.util.List;

+

+/**

+ * Helper for computing intersection of {@link Property} arrays.

+ * 

+ * @author scheglov_ke

+ * @coverage core.editor.structure

+ */

+public final class PropertyListIntersector {

+  private List<PropertyGroup> m_intersection;

+

+  ////////////////////////////////////////////////////////////////////////////

+  //

+  // Access

+  //

+  ////////////////////////////////////////////////////////////////////////////

+  /**

+   * Updates intersection by intersecting with new given array.

+   */

+  public void intersect(Property[] properties) {

+    if (m_intersection == null) {

+      m_intersection = Lists.newArrayList();

+      for (int i = 0; i < properties.length; i++) {

+        Property property = properties[i];

+        m_intersection.add(new PropertyGroup(property));

+      }

+    } else {

+      for (Iterator<PropertyGroup> I = m_intersection.iterator(); I.hasNext();) {

+        PropertyGroup propertyGroup = I.next();

+        if (!propertyGroup.add(properties)) {

+          I.remove();

+        }

+      }

+    }

+  }

+

+  /**

+   * @return the array of matched composite {@link Property}'s.

+   */

+  public Property[] getProperties() {

+    List<Property> properties = Lists.newArrayList();

+    for (PropertyGroup propertyGroup : m_intersection) {

+      Property compositeProperty = propertyGroup.getCompositeProperty();

+      if (compositeProperty != null) {

+        properties.add(compositeProperty);

+      }

+    }

+    //

+    return properties.toArray(new Property[properties.size()]);

+  }

+

+  ////////////////////////////////////////////////////////////////////////////

+  //

+  // PropertyGroup

+  //

+  ////////////////////////////////////////////////////////////////////////////

+  /**

+   * The group of {@link Property}'s that match.

+   */

+  private static final class PropertyGroup {

+    private final List<Property> m_properties = Lists.newArrayList();

+

+    ////////////////////////////////////////////////////////////////////////////

+    //

+    // Constructor

+    //

+    ////////////////////////////////////////////////////////////////////////////

+    public PropertyGroup(Property property) {

+      m_properties.add(property);

+    }

+

+    ////////////////////////////////////////////////////////////////////////////

+    //

+    // Access

+    //

+    ////////////////////////////////////////////////////////////////////////////

+    /**

+     * @return <code>true</code> if new matched {@link Property} from given array was added.

+     */

+    public boolean add(Property[] properties) {

+      for (Property property : properties) {

+        if (add(property)) {

+          return true;

+        }

+      }

+      // no match

+      return false;

+    }

+

+    /**

+     * @return the composite {@link Property} for this group.

+     */

+    public Property getCompositeProperty() {

+      Property properties[] = m_properties.toArray(new Property[m_properties.size()]);

+      return properties[0].getComposite(properties);

+    }

+

+    ////////////////////////////////////////////////////////////////////////////

+    //

+    // Internal

+    //

+    ////////////////////////////////////////////////////////////////////////////

+    /**

+     * @return <code>true</code> if given {@link Property} matches and was added.

+     */

+    private boolean add(Property property) {

+      Property example = m_properties.get(0);

+      if (example.getClass() == property.getClass()

+          && example.getTitle().equals(property.getTitle())

+          && PropertyManager.getCategory(example) == PropertyManager.getCategory(property)) {

+        m_properties.add(property);

+        return true;

+      }

+      // no match

+      return false;

+    }

+  }

+}

diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanNull.png b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanNull.png
new file mode 100644
index 0000000..437b546
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanNull.png
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanUnknown.png b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanUnknown.png
new file mode 100644
index 0000000..e92cbf0
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/BooleanUnknown.png
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/dots.gif b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/dots.gif
new file mode 100644
index 0000000..e3c6e83
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/dots.gif
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/down.png b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/down.png
new file mode 100644
index 0000000..57147b8
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/down.png
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/false.png b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/false.png
new file mode 100644
index 0000000..7e1094f
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/false.png
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/minus.gif b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/minus.gif
new file mode 100644
index 0000000..e6579a6
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/minus.gif
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/plus.gif b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/plus.gif
new file mode 100644
index 0000000..3944fed
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/plus.gif
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/true.png b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/true.png
new file mode 100644
index 0000000..7908e7a
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/icons/properties/true.png
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.java b/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.java
new file mode 100644
index 0000000..58bc631
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.java
@@ -0,0 +1,27 @@
+package org.eclipse.wb.internal.core.model;
+
+import org.eclipse.osgi.util.NLS;
+
+public class ModelMessages extends NLS {
+  private static final String BUNDLE_NAME = "org.eclipse.wb.internal.core.model.ModelMessages"; //$NON-NLS-1$
+  public static String CharacterPropertyEditor_notValid;
+  public static String DoubleObjectPropertyEditor_notValidDouble;
+  public static String DoublePropertyEditor_notValidDouble;
+  public static String FloatPropertyEditor_notValidFloat;
+  public static String IntegerObjectPropertyEditor_notValidInt;
+  public static String IntegerPropertyEditor_notValidInt;
+  public static String LongObjectPropertyEditor_notValidLong;
+  public static String LongPropertyEditor_notValidLong;
+  public static String ShortObjectPropertyEditor_notValidShort;
+  public static String ShortPropertyEditor_notValidShort;
+  public static String StringArrayPropertyEditor_hint;
+  public static String StringArrayPropertyEditor_itemsLabel;
+  public static String StringPropertyDialog_title;
+  static {
+    // initialize resource bundle
+    NLS.initializeMessages(BUNDLE_NAME, ModelMessages.class);
+  }
+
+  private ModelMessages() {
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.properties b/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.properties
new file mode 100644
index 0000000..6801adb
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/ModelMessages.properties
@@ -0,0 +1,13 @@
+CharacterPropertyEditor_notValid="{0}" is not a valid character.
+DoubleObjectPropertyEditor_notValidDouble="{0}" is not a valid double.
+DoublePropertyEditor_notValidDouble="{0}" is not a valid double.
+FloatPropertyEditor_notValidFloat="{0}" is not a valid float.
+IntegerObjectPropertyEditor_notValidInt="{0}" is not a valid int.
+IntegerPropertyEditor_notValidInt="{0}" is not a valid int.
+LongObjectPropertyEditor_notValidLong="{0}" is not a valid long.
+LongPropertyEditor_notValidLong="{0}" is not a valid long.
+ShortObjectPropertyEditor_notValidShort="{0}" is not a valid short.
+ShortPropertyEditor_notValidShort="{0}" is not a valid short.
+StringArrayPropertyEditor_hint=Each line in the above text field represents single element.
+StringArrayPropertyEditor_itemsLabel=&Elements:
+StringPropertyDialog_title=String editor
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/ComplexProperty.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/ComplexProperty.java
new file mode 100644
index 0000000..69bb455
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/ComplexProperty.java
@@ -0,0 +1,209 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property;
+
+import org.eclipse.wb.internal.core.model.property.editor.TextDisplayPropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.complex.IComplexPropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTooltipProvider;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTooltipTextProvider;
+
+import org.eclipse.swt.graphics.Point;
+
+import java.util.List;
+
+/**
+ * Implementation of {@link Property} that shows given inner {@link Property}'s using
+ * {@link IComplexPropertyEditor}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property
+ */
+public class ComplexProperty extends Property {
+  private final String m_title;
+  private String m_text;
+  private String m_tooltip;
+  private boolean m_modified;
+  private Property[] m_properties;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructors
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public ComplexProperty(String title, String text) {
+    this(title, text, new Property[0]);
+  }
+
+  public ComplexProperty(String title, String text, Property[] properties) {
+    super(new ComplexPropertyEditor());
+    m_title = title;
+    m_text = text;
+    setText(text);
+    setProperties(properties);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets the text.
+   */
+  public void setText(String text) {
+    m_text = text;
+  }
+
+  /**
+   * @return the text to display as value.
+   */
+  public String getText() throws Exception {
+    return m_text;
+  }
+
+  /**
+   * Sets the tooltip text.
+   */
+  public void setTooltip(String tooltip) {
+    m_tooltip = tooltip;
+  }
+
+  /**
+   * Specifies the {@link PropertyEditorPresentation}, for example to displaying "..." button.
+   */
+  public void setEditorPresentation(PropertyEditorPresentation presentation) {
+    ((ComplexPropertyEditor) getEditor()).m_presentation = presentation;
+  }
+
+  /**
+   * @return the sub-properties.
+   */
+  public Property[] getProperties() {
+    return m_properties;
+  }
+
+  /**
+   * Sets the sub-properties.
+   */
+  public void setProperties(Property[] properties) {
+    m_properties = properties;
+  }
+
+  /**
+   * Sets the sub-properties.
+   */
+  public void setProperties(List<Property> properties) {
+    Property[] propertiesArray = properties.toArray(new Property[properties.size()]);
+    setProperties(propertiesArray);
+  }
+
+  /**
+   * Sets the "modified" flag.
+   */
+  public void setModified(boolean modified) {
+    m_modified = modified;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Property
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getTitle() {
+    return m_title;
+  }
+
+  @Override
+  public boolean isModified() throws Exception {
+    return m_modified;
+  }
+
+  @Override
+  public Object getValue() throws Exception {
+    return null;
+  }
+
+  @Override
+  public void setValue(Object value) throws Exception {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Adapter
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public <T> T getAdapter(Class<T> adapter) {
+    if (adapter == PropertyTooltipProvider.class && m_tooltip != null) {
+      return adapter.cast(new PropertyTooltipTextProvider() {
+        @Override
+        protected String getText(Property property) throws Exception {
+          return m_tooltip;
+        }
+      });
+    }
+    return super.getAdapter(adapter);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // ComplexPropertyEditor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private static final class ComplexPropertyEditor extends TextDisplayPropertyEditor
+      implements
+        IComplexPropertyEditor {
+    private PropertyEditorPresentation m_presentation;
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // IComplexPropertyEditor
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    public Property[] getProperties(Property property) throws Exception {
+      return ((ComplexProperty) property).getProperties();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // TextDisplayPropertyEditor
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    @Override
+    protected String getText(Property property) throws Exception {
+      return ((ComplexProperty) property).getText();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // PropertyEditor
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    @Override
+    public boolean activate(PropertyTable propertyTable, Property property, Point location)
+        throws Exception {
+      return false;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Presentation
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    @Override
+    public PropertyEditorPresentation getPresentation() {
+      return m_presentation;
+    }
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/EmptyProperty.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/EmptyProperty.java
new file mode 100644
index 0000000..0f12a5a
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/EmptyProperty.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property;
+
+import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.string.StringPropertyEditor;
+
+/**
+ * Empty {@link Property}, that has no title or value.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property
+ */
+public class EmptyProperty extends Property {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public EmptyProperty() {
+    super(StringPropertyEditor.INSTANCE);
+  }
+
+  public EmptyProperty(PropertyEditor editor) {
+    super(editor);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Property
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getTitle() {
+    return null;
+  }
+
+  @Override
+  public boolean isModified() throws Exception {
+    return false;
+  }
+
+  @Override
+  public Object getValue() throws Exception {
+    return UNKNOWN_VALUE;
+  }
+
+  @Override
+  public void setValue(Object value) throws Exception {
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/Property.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/Property.java
new file mode 100644
index 0000000..28afcd3
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/Property.java
@@ -0,0 +1,232 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property;
+
+import com.google.common.collect.Maps;
+
+import org.eclipse.wb.internal.core.model.property.category.PropertyCategory;
+import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
+
+import java.util.Comparator;
+import java.util.Map;
+
+/**
+ * {@link Property} is used to display/change properties of ObjectInfo's.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property
+ */
+public abstract class Property {
+  /**
+   * The value that should be used when we don't know real value of {@link Property}. We can not use
+   * <code>null</code> because <code>null</code> can be valid value.
+   */
+  public static final Object UNKNOWN_VALUE = new Object() {
+    @Override
+    public String toString() {
+      return "UNKNOWN_VALUE";
+    }
+  };
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance fields
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected final PropertyEditor m_editor;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public Property(PropertyEditor editor) {
+    m_category = PropertyCategory.NORMAL;
+    m_editor = editor;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the title displayed to the user to identify the property.
+   */
+  public abstract String getTitle();
+
+  /**
+   * @return <code>true</code> if this property has a non-default value
+   */
+  public abstract boolean isModified() throws Exception;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Category
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private PropertyCategory m_category;
+
+  /**
+   * @return current {@link PropertyCategory}.
+   */
+  public final PropertyCategory getCategory() {
+    return m_category;
+  }
+
+  /**
+   * Sets the {@link PropertyCategory} for this {@link Property}.
+   */
+  public final void setCategory(PropertyCategory category) {
+    m_category = category;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Value
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the current value of this {@link Property} or {@link #UNKNOWN_VALUE}.
+   */
+  public abstract Object getValue() throws Exception;
+
+  /**
+   * Sets the new value of this {@link Property}.
+   *
+   * @param the
+   *          new value of {@link Property} or {@link #UNKNOWN_VALUE} if {@link Property}
+   *          modification should be removed.
+   */
+  public abstract void setValue(Object value) throws Exception;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the {@link PropertyEditor}.
+   */
+  public final PropertyEditor getEditor() {
+    return m_editor;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Composite
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the composite {@link Property} for given array of {@link Property}'s or
+   *         <code>null</code> if no composite {@link Property} can be created.
+   */
+  public Property getComposite(Property[] properties) {
+    return null;
+  }
+
+  public <T> T getAdapter(Class<T> adapter) {
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Arbitrary values map
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private Map<Object, Object> m_arbitraryMap;
+
+  /**
+   * Associates the given value with the given key.
+   */
+  public final void putArbitraryValue(Object key, Object value) {
+    if (m_arbitraryMap == null) {
+      m_arbitraryMap = Maps.newHashMap();
+    }
+    m_arbitraryMap.put(key, value);
+  }
+
+  /**
+   * @return the value to which the given key is mapped, or <code>null</code>.
+   */
+  public final Object getArbitraryValue(Object key) {
+    if (m_arbitraryMap != null) {
+      return m_arbitraryMap.get(key);
+    }
+    return null;
+  }
+
+  /**
+   * Removes the mapping for a key.
+   */
+  public final void removeArbitraryValue(Object key) {
+    if (m_arbitraryMap != null) {
+      m_arbitraryMap.remove(key);
+    }
+  }
+
+  // BEGIN ADT MODIFICATIONS
+
+    /**
+     * Returns the name of the property (which is not always the same as the
+     * title; for example, the "maxWidth" property has title "Max Width" and
+     * name "maxWidth".
+     * <p>
+     * This is shown in tooltips to users etc to make it clear what they should
+     * use in their own code.
+     *
+     * @return the name of the property
+     */
+  public String getName() {
+      return getTitle();
+  }
+
+  private int mPriority;
+
+  /**
+   * Gets the custom sort priority of this property
+   *
+   * @return the sort priority
+   */
+  public int getPriority() {
+      return mPriority;
+  }
+
+  /**
+   * Sets the custom sort priority of this property
+   *
+   * @param priority the new priority to use
+   */
+  public void setPriority(int priority) {
+      this.mPriority = priority;
+  }
+
+  /** Sort {@link Property} instances alphabetically by property name */
+  public static final Comparator<Property> ALPHABETICAL = new Comparator<Property>() {
+      @Override
+      public int compare(Property p1, Property p2) {
+          return p1.getName().compareTo(p2.getName());
+      }
+  };
+
+  /** Sort {@link Property} instances by priority */
+  public static final Comparator<Property> PRIORITY = new Comparator<Property>() {
+      @Override
+      public int compare(Property p1, Property p2) {
+          int delta = p1.mPriority - p2.mPriority;
+          if (delta != 0) {
+              return delta;
+          }
+
+          return p1.getName().compareTo(p2.getName());
+      }
+  };
+  // END ADT MODIFICATIONS
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/PropertyManager.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/PropertyManager.java
new file mode 100644
index 0000000..7152999
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/PropertyManager.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property;
+
+import org.eclipse.wb.internal.core.model.property.category.PropertyCategory;
+
+
+/**
+ * {@link PropertyManager} is used to get/set attributes of {@link Property}.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property
+ */
+public final class PropertyManager {
+  public static PropertyCategory getCategory(Property property) {
+    // Note: In WindowBuilder there was a bunch of support for loading custom
+    // categories here based on toolkits; in ADT we'll need to do it differently
+    // so this code was all stripped out.
+    return property.getCategory();
+  }
+
+  /**
+   * @return the forced {@link PropertyCategory} of given Property, may be <code>null</code>.
+   */
+  public static PropertyCategory getCategoryForced(Property property) {
+    return null;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategory.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategory.java
new file mode 100644
index 0000000..a135b03
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategory.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.category;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.check.Assert;
+
+/**
+ * Describes category of {@link Property}.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property
+ */
+public final class PropertyCategory {
+  /**
+   * "Normal" category, used for properties that should be displayed without any effect.
+   */
+  public static final PropertyCategory NORMAL = new PropertyCategory(0, "NORMAL");
+  /**
+   * "Preferred" category, for properties that are most useful for component.
+   */
+  public static final PropertyCategory PREFERRED = new PropertyCategory(-1, "PREFERRED");
+  /**
+   * "Advanced" category, for properties that are rarely used, visible if modified, even if not
+   * enabled.
+   */
+  public static final PropertyCategory ADVANCED = new PropertyCategory(1, "ADVANCED");
+  /**
+   * "Advanced" category, for properties that are rarely used, visible only if enabled.
+   */
+  public static final PropertyCategory ADVANCED_REALLY = new PropertyCategory(2, "ADVANCED_REALLY");
+  /**
+   * "Hidden" category, for properties that should not be displayed.
+   */
+  public static final PropertyCategory HIDDEN = new PropertyCategory(3, "HIDDEN");
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // System
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the system {@link PropertyCategory} with given priority.
+   */
+  public static final PropertyCategory system(int priority) {
+    return new PropertyCategory(SYSTEM_BASE + priority, "SYSTEM:" + priority);
+  }
+
+  /**
+   * @return the system {@link PropertyCategory} with priority
+   *         <code>system.getPriority() + additional</code>.
+   */
+  public static final PropertyCategory system(PropertyCategory system, int additional) {
+    Assert.isTrue(system.isSystem());
+    return system(system.getPriority() - SYSTEM_BASE + additional);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance fields
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private static final int SYSTEM_BASE = 1000;
+  private final int m_priority;
+  private final String m_string;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private PropertyCategory(int priority, String string) {
+    m_priority = priority;
+    m_string = string;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Object
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String toString() {
+    return m_string;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    }
+    if (obj instanceof PropertyCategory) {
+      PropertyCategory category = (PropertyCategory) obj;
+      return m_priority == category.m_priority;
+    }
+    // unknown class
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return m_priority;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return <code>true</code> if this property is preferred.
+   */
+  public boolean isPreferred() {
+    return this == PREFERRED;
+  }
+
+  /**
+   * @return <code>true</code> if this property is advanced.
+   */
+  public boolean isAdvanced() {
+    return this == ADVANCED;
+  }
+
+  /**
+   * @return <code>true</code> if this property is really advanced.
+   */
+  public boolean isAdvancedReally() {
+    return this == ADVANCED_REALLY;
+  }
+
+  /**
+   * @return <code>true</code> if this property is hidden.
+   */
+  public boolean isHidden() {
+    return this == HIDDEN;
+  }
+
+  /**
+   * @return <code>true</code> if this property is system.
+   */
+  public boolean isSystem() {
+    return m_priority >= 900;
+  }
+
+  /**
+   * @return the priority of this category.
+   */
+  public int getPriority() {
+    return m_priority;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProvider.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProvider.java
new file mode 100644
index 0000000..b435576
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProvider.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.category;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+
+/**
+ * This interface is used to get {@link PropertyCategory} for {@link Property}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property
+ */
+public interface PropertyCategoryProvider {
+  /**
+   * @return the {@link PropertyCategory} of given Property, not <code>null</code>.
+   */
+  PropertyCategory getCategory(Property property);
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProviders.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProviders.java
new file mode 100644
index 0000000..83aaebb
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/category/PropertyCategoryProviders.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.category;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.PropertyManager;
+
+/**
+ * Factory for {@link PropertyCategoryProvider} instances.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property
+ */
+public final class PropertyCategoryProviders {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Simple providers
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private static final PropertyCategoryProvider FROM_PROPERTY = new PropertyCategoryProvider() {
+    public PropertyCategory getCategory(Property property) {
+      return property.getCategory();
+    }
+  };
+
+  /**
+   * Returns result of {@link Property#getCategory()}, never <code>null</code>.
+   */
+  public static PropertyCategoryProvider fromProperty() {
+    return FROM_PROPERTY;
+  }
+
+  private static final PropertyCategoryProvider FORCED_BY_USER = new PropertyCategoryProvider() {
+    public PropertyCategory getCategory(Property property) {
+      return PropertyManager.getCategoryForced(property);
+    }
+  };
+
+  /**
+   * Returns category forced by user, may be <code>null</code>.
+   */
+  public static PropertyCategoryProvider forcedByUser() {
+    return FORCED_BY_USER;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Compound
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Returns first not <code>null</code> category returned by provider.
+   */
+  public static PropertyCategoryProvider combine(final PropertyCategoryProvider... providers) {
+    return new PropertyCategoryProvider() {
+      public PropertyCategory getCategory(Property property) {
+        for (PropertyCategoryProvider provider : providers) {
+          PropertyCategory category = provider.getCategory(property);
+          if (category != null) {
+            return category;
+          }
+        }
+        throw new IllegalStateException("Can not provide category for " + property.getTitle());
+      }
+    };
+  }
+
+  private static final PropertyCategoryProvider DEF = combine(forcedByUser(), fromProperty());
+
+  /**
+   * Returns the default combination of {@link PropertyCategoryProvider}s - first
+   * {@link #forcedByUser()}, then {@link #fromProperty()}.
+   */
+  public static PropertyCategoryProvider def() {
+    return DEF;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboBoxPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboBoxPropertyEditor.java
new file mode 100644
index 0000000..f122381
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboBoxPropertyEditor.java
@@ -0,0 +1,156 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.core.controls.CCombo3;
+import org.eclipse.wb.core.controls.CComboBox;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
+import org.eclipse.wb.internal.core.utils.execution.RunnableEx;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusAdapter;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+
+/**
+ * The {@link PropertyEditor} for selecting single value using {@link CComboBox}. This editor has
+ * in-line search-feature and is more suitable (vs {@link AbstractComboPropertyEditor}) for
+ * properties with large lists of value items.
+ * 
+ * @author sablin_aa
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public abstract class AbstractComboBoxPropertyEditor extends TextDisplayPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private CComboBox m_combo;
+  private String m_dropDelayedText;
+
+  @Override
+  public final boolean activate(final PropertyTable propertyTable,
+      final Property property,
+      Point location) throws Exception {
+    m_combo = new CComboBox(propertyTable, SWT.NONE);
+    // initialize
+    addItems(property, m_combo);
+    selectItem(property, m_combo);
+    // install listeners
+    m_combo.addKeyListener(new KeyAdapter() {
+      @Override
+      public void keyPressed(KeyEvent e) {
+        handleKeyPressed(propertyTable, property, e);
+      }
+    });
+    m_combo.addFocusListener(new FocusAdapter() {
+      @Override
+      public void focusLost(FocusEvent e) {
+        propertyTable.deactivateEditor(true);
+      }
+    });
+    m_combo.addSelectionListener(new SelectionAdapter() {
+      @Override
+      public void widgetSelected(SelectionEvent e) {
+        propertyTable.deactivateEditor(true);
+      }
+    });
+    m_combo.setFocus();
+    // schedule showing drop-down, because we don't have bounds yet
+    ExecutionUtils.runAsync(new RunnableEx() {
+      public void run() throws Exception {
+        m_combo.comboDropDown(true);
+        if (m_dropDelayedText != null) {
+          m_combo.setEditText(m_dropDelayedText);
+          m_combo.setEditSelection(m_dropDelayedText.length(), m_dropDelayedText.length());
+          m_dropDelayedText = null;
+        }
+      }
+    });
+    // keep editor active
+    return true;
+  }
+
+  private void handleKeyPressed(PropertyTable propertyTable, Property property, KeyEvent e) {
+    if (e.keyCode == SWT.ESC) {
+      propertyTable.deactivateEditor(false);
+    } else if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) {
+      e.doit = false;
+      propertyTable.deactivateEditor(true);
+      propertyTable.navigate(e);
+    }
+  }
+
+  @Override
+  public final void deactivate(PropertyTable propertyTable, Property property, boolean save) {
+    if (save) {
+      toProperty(propertyTable, property);
+    }
+    if (m_combo != null) {
+      m_combo.dispose();
+      m_combo = null;
+    }
+  }
+
+  private void toProperty(PropertyTable propertyTable, Property property) {
+    try {
+      toPropertyEx(property, m_combo);
+    } catch (Throwable e) {
+      propertyTable.handleException(e);
+    }
+  }
+
+  @Override
+  public void setBounds(Rectangle bounds) {
+    m_combo.setBounds(bounds);
+  }
+
+  @Override
+  public void keyDown(PropertyTable propertyTable, Property property, KeyEvent event)
+      throws Exception {
+    boolean withAlt = (event.stateMask & SWT.ALT) != 0;
+    boolean withCtrl = (event.stateMask & SWT.CTRL) != 0;
+    if (event.character > 0x20 && !(withAlt || withCtrl)) {
+      propertyTable.activateEditor(property, null);
+      m_dropDelayedText = "" + event.character;
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Abstract methods
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Adds items to given {@link CComboBox}.
+   */
+  protected abstract void addItems(Property property, CComboBox combo) throws Exception;
+
+  /**
+   * Selects current item in given {@link CCombo3}.
+   */
+  protected void selectItem(Property property, CComboBox combo) throws Exception {
+  }
+
+  /**
+   * Transfers data from widget to {@link Property}.
+   */
+  protected abstract void toPropertyEx(Property property, CComboBox combo) throws Exception;
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboPropertyEditor.java
new file mode 100644
index 0000000..a225f45
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractComboPropertyEditor.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.core.controls.CCombo3;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusAdapter;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+/**
+ * The {@link PropertyEditor} for selecting single value using {@link CCombo3}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public abstract class AbstractComboPropertyEditor extends TextDisplayPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private CCombo3 m_combo;
+  private boolean m_doDropDown;
+
+  @Override
+  public boolean activate(final PropertyTable propertyTable, final Property property, Point location)
+      throws Exception {
+    // create combo
+    {
+      m_combo = new CCombo3(propertyTable, SWT.NONE);
+      m_doDropDown = true;
+      // add items
+      addItems(property, m_combo);
+      // select item
+      selectItem(property, m_combo);
+    }
+    // add listeners
+    m_combo.addFocusListener(new FocusAdapter() {
+      @Override
+      public void focusLost(FocusEvent e) {
+        propertyTable.deactivateEditor(true);
+      }
+    });
+    m_combo.addSelectionListener(new SelectionAdapter() {
+      @Override
+      public void widgetSelected(SelectionEvent e) {
+        int index = m_combo.getSelectionIndex();
+        toProperty(propertyTable, property, index);
+      }
+    });
+    m_combo.addListener(SWT.KeyDown, new Listener() {
+      public void handleEvent(Event event) {
+        switch (event.keyCode) {
+          case SWT.ESC :
+            propertyTable.deactivateEditor(false);
+            break;
+          case SWT.DEL :
+            try {
+              property.setValue(Property.UNKNOWN_VALUE);
+              event.doit = false;
+              selectItem(property, m_combo);
+            } catch (Throwable e) {
+              propertyTable.handleException(e);
+              propertyTable.deactivateEditor(false);
+            }
+            m_combo.doDropDown(false);
+            break;
+        }
+      }
+    });
+    m_combo.addMouseListener(new MouseAdapter() {
+      @Override
+      public void mouseDoubleClick(MouseEvent e) {
+        int index = (m_combo.getSelectionIndex() + 1) % m_combo.getItemCount();
+        toProperty(propertyTable, property, index);
+      }
+    });
+    // keep editor active
+    return true;
+  }
+
+  @Override
+  public final void setBounds(Rectangle bounds) {
+    m_combo.setBounds(bounds);
+    // editor created without bounds, so activate it after first setBounds()
+    if (m_doDropDown) {
+      m_doDropDown = false;
+      m_combo.setFocus();
+      m_combo.doDropDown(true);
+      m_combo.startDrag();
+    }
+  }
+
+  @Override
+  public final void deactivate(PropertyTable propertyTable, Property property, boolean save) {
+    if (m_combo != null) {
+      m_combo.dispose();
+      m_combo = null;
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Abstract methods
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Adds items to given {@link CCombo3}.
+   */
+  protected abstract void addItems(Property property, CCombo3 combo) throws Exception;
+
+  /**
+   * Selects current item in given {@link CCombo3}.
+   */
+  protected abstract void selectItem(Property property, CCombo3 combo) throws Exception;
+
+  /**
+   * Transfers data from widget to {@link Property}.
+   */
+  protected abstract void toPropertyEx(Property property, CCombo3 combo, int index)
+      throws Exception;
+
+  /**
+   * Transfers data from widget to {@link Property}.
+   */
+  private void toProperty(PropertyTable propertyTable, Property property, int index) {
+    try {
+      toPropertyEx(property, m_combo, index);
+    } catch (Throwable e) {
+      propertyTable.handleException(e);
+    }
+    propertyTable.deactivateEditor(false);
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractListPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractListPropertyEditor.java
new file mode 100644
index 0000000..ba34103
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractListPropertyEditor.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.core.controls.CCombo3;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.check.Assert;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The {@link PropertyEditor} for selecting single expression from given set.
+ *
+ * @author sablin_aa
+ * @coverage core.model.property.editor
+ */
+public abstract class AbstractListPropertyEditor extends AbstractComboPropertyEditor
+    implements
+      IValueSourcePropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // TextDisplayPropertyEditor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    // return title for value
+    Object value = property.getValue();
+    if (value != Property.UNKNOWN_VALUE) {
+      int index = getValueIndex(value);
+      if (index >= 0) {
+        return getTitle(index);
+      } else {
+        if (value instanceof String) {
+          return (String) value;
+        }
+      }
+    }
+    // unknown value
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // IValueSourcePropertyEditor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+public String getValueSource(Object value) throws Exception {
+    // return expression for value
+    if (value != Property.UNKNOWN_VALUE) {
+      int index = getValueIndex(value);
+      if (index >= 0) {
+        return getExpression(index);
+      }
+    }
+    // unknown value
+    return null;
+  }
+
+//  ////////////////////////////////////////////////////////////////////////////
+//  //
+//  // IClipboardSourceProvider
+//  //
+//  ////////////////////////////////////////////////////////////////////////////
+//  @Override
+//public String getClipboardSource(GenericProperty property) throws Exception {
+//    Object value = property.getValue();
+//    return getValueSource(value);
+//  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Combo
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected void addItems(Property property, CCombo3 combo) throws Exception {
+    for (int i = 0; i < getCount(); i++) {
+      combo.add(getTitle(i));
+    }
+  }
+
+  @Override
+  protected void selectItem(Property property, CCombo3 combo) throws Exception {
+    combo.setText(getText(property));
+  }
+
+  @Override
+  protected void toPropertyEx(Property property, CCombo3 combo, int index) throws Exception {
+//    if (property instanceof GenericProperty) {
+//      GenericProperty genericProperty = (GenericProperty) property;
+//      String expression = getExpression(index);
+//      Object evaluatedExpression = evaluateExpression(genericProperty, expression);
+//      // apply expression
+//      genericProperty.setExpression(expression, evaluatedExpression);
+//    } else {
+      toPropertyEx_simpleProperty(property, combo, index);
+//    }
+  }
+
+//  private static Object evaluateExpression(final GenericProperty genericProperty,
+//      final String expression) {
+//    return ExecutionUtils.runObjectIgnore(new RunnableObjectEx<Object>() {
+//      public Object runObject() throws Exception {
+//        JavaInfo javaInfo = genericProperty.getJavaInfo();
+//        ClassLoader classLoader = JavaInfoUtils.getClassLoader(javaInfo);
+//        return ScriptUtils.evaluate(classLoader, expression);
+//      }
+//    }, Property.UNKNOWN_VALUE);
+//      System.out.println("HACK 1234");
+//      return Property.UNKNOWN_VALUE;
+//  }
+
+  /**
+   * Sets value of simple {@link Property}, not {@link GenericProperty}.
+   */
+  protected void toPropertyEx_simpleProperty(Property property, CCombo3 combo, int index)
+      throws Exception {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access to list items
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  abstract protected int getCount();
+
+  abstract protected int getValueIndex(Object value);
+
+  abstract protected String getTitle(int index);
+
+  abstract protected String getExpression(int index) throws Exception;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Extract string array from parameters.
+   */
+  protected static String[] getParameterAsArray(Map<String, Object> parameters, String name) {
+    return getParameterAsArray(parameters, name, false);
+  }
+
+  @SuppressWarnings("unchecked")
+  protected static String[] getParameterAsArray(Map<String, Object> parameters,
+      String name,
+      boolean noAssert) {
+    String[] values = null;
+    if (parameters.containsKey(name)) {
+      List<String> list = (List<String>) parameters.get(name);
+      values = list.toArray(new String[list.size()]);
+    } else {
+      if (noAssert) {
+        values = null;
+      } else {
+        Assert.fail(String.format("No parameter %s in %s.", name, parameters));
+      }
+    }
+    return values;
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractTextPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractTextPropertyEditor.java
new file mode 100644
index 0000000..1cf9574
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractTextPropertyEditor.java
@@ -0,0 +1,306 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.jface.bindings.keys.KeyStroke;
+import org.eclipse.jface.fieldassist.ContentProposalAdapter;
+import org.eclipse.jface.fieldassist.IContentProposalProvider;
+import org.eclipse.jface.fieldassist.IControlContentAdapter;
+import org.eclipse.jface.fieldassist.TextContentAdapter;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+/**
+ * Abstract {@link PropertyEditor} for that uses {@link Text} as control.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public abstract class AbstractTextPropertyEditor extends TextDisplayPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private Text m_textControl;
+  private boolean m_ignoreFocusLost;
+
+  // BEGIN ADT MODIFICATIONS
+  // ContentProposalAdapter which exposes the openProposalPopup method such
+  // that we can open the dialog up immediately on focus gain to show all available
+  // alternatives (the default implementation requires at least one keytroke before
+  // it shows up)
+    private static class ImmediateProposalAdapter extends ContentProposalAdapter {
+        public ImmediateProposalAdapter(Text control,
+                IControlContentAdapter controlContentAdapter,
+                IContentProposalProvider proposalProvider, KeyStroke keyStroke,
+                char[] autoActivationCharacters) {
+            super(control, controlContentAdapter, proposalProvider, keyStroke,
+                    autoActivationCharacters);
+
+            // On focus gain, start completing
+            control.addFocusListener(new FocusListener() {
+                @Override
+                public void focusGained(FocusEvent event) {
+                    openIfNecessary();
+                }
+
+                @Override
+                public void focusLost(FocusEvent event) {
+                }
+            });
+
+            /* Triggering on empty is disabled for now: it has the unfortunate side-effect
+               that it's impossible to enter a blank text field - blank matches everything,
+               so the first item will automatically be selected when you press return.
+
+
+            // If you edit the text and delete everything, the normal implementation
+            // will close the popup; we'll reopen it
+            control.addModifyListener(new ModifyListener() {
+                @Override
+                public void modifyText(ModifyEvent event) {
+                    if (((Text) getControl()).getText().isEmpty()) {
+                        openIfNecessary();
+                    }
+                }
+            });
+            */
+        }
+
+        private void openIfNecessary() {
+            getControl().getDisplay().asyncExec(new Runnable() {
+                @Override
+                public void run() {
+                    if (!isProposalPopupOpen()) {
+                        openProposalPopup();
+                    }
+                }
+            });
+        }
+    }
+  // END ADT MODIFICATIONS
+
+  @Override
+  public boolean activate(final PropertyTable propertyTable, final Property property, Point location)
+      throws Exception {
+    // create Text
+    {
+      m_textControl = new Text(propertyTable, SWT.NONE);
+      new TextControlActionsManager(m_textControl);
+      m_textControl.setEditable(isEditable());
+
+        // BEGIN ADT MODIFICATIONS
+        // Add support for field completion, if the property provides an IContentProposalProvider
+        // via its the getAdapter method.
+        IContentProposalProvider completion = property.getAdapter(IContentProposalProvider.class);
+        if (completion != null) {
+            ImmediateProposalAdapter adapter = new ImmediateProposalAdapter(
+                    m_textControl, new TextContentAdapter(), completion, null, null);
+            adapter.setFilterStyle(ContentProposalAdapter.FILTER_NONE);
+            adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
+            ILabelProvider labelProvider = property.getAdapter(ILabelProvider.class);
+            if (labelProvider != null) {
+                adapter.setLabelProvider(labelProvider);
+            }
+        }
+        // END ADT MODIFICATIONS
+      m_textControl.setFocus();
+    }
+    // add listeners
+    m_textControl.addKeyListener(new KeyAdapter() {
+      @Override
+      public void keyPressed(KeyEvent e) {
+        try {
+          handleKeyPressed(propertyTable, property, e);
+        } catch (Throwable ex) {
+          propertyTable.deactivateEditor(false);
+          propertyTable.handleException(ex);
+        }
+      }
+    });
+    m_textControl.addListener(SWT.FocusOut, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        if (!m_ignoreFocusLost) {
+          propertyTable.deactivateEditor(true);
+        }
+      }
+    });
+    // set data
+    toWidget(property);
+    // keep us active
+    return true;
+  }
+
+  @Override
+  public final void setBounds(Rectangle bounds) {
+    m_textControl.setBounds(bounds);
+  }
+
+  @Override
+  public final void deactivate(PropertyTable propertyTable, Property property, boolean save) {
+    if (save) {
+      try {
+        toProperty(property);
+      } catch (Throwable e) {
+        propertyTable.deactivateEditor(false);
+        propertyTable.handleException(e);
+      }
+    }
+    // dispose Text widget
+    if (m_textControl != null) {
+      m_textControl.dispose();
+      m_textControl = null;
+    }
+  }
+
+  @Override
+  public void keyDown(PropertyTable propertyTable, Property property, KeyEvent event)
+      throws Exception {
+    boolean withAlt = (event.stateMask & SWT.ALT) != 0;
+    boolean withCtrl = (event.stateMask & SWT.CTRL) != 0;
+    if (event.character != 0 && !(withAlt || withCtrl)) {
+      propertyTable.activateEditor(property, null);
+      postKeyEvent(SWT.KeyDown, event);
+      postKeyEvent(SWT.KeyUp, event);
+    }
+  }
+
+  /**
+   * Posts low-level {@link SWT.KeyDown} or {@link SWT.KeyUp} event.
+   */
+  private static void postKeyEvent(int type, KeyEvent event) {
+    Event lowEvent = new Event();
+    lowEvent.type = type;
+    lowEvent.keyCode = event.keyCode;
+    lowEvent.character = event.character;
+    // post event
+    Display.getCurrent().post(lowEvent);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Events
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Handles {@link KeyListener#keyPressed(KeyEvent)}.
+   */
+  private void handleKeyPressed(PropertyTable propertyTable, Property property, KeyEvent e)
+      throws Exception {
+    if (e.keyCode == SWT.CR) {
+      toProperty(property);
+
+      // BEGIN ADT MODIFICATIONS
+      // After pressing return, dismiss the text cursor to make the value "committed".
+      // I'm not sure why this is necessary here and not in WindowBuilder proper.
+      propertyTable.deactivateEditor(true);
+      // END ADT MODIFICATIONS
+    } else if (e.keyCode == SWT.ESC) {
+      propertyTable.deactivateEditor(false);
+    } else if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) {
+      e.doit = false;
+      boolean success = toProperty(property);
+      // don't allow navigation if current text can not be transferred to property
+      if (!success) {
+        return;
+      }
+      // OK, deactivate and navigate
+      propertyTable.deactivateEditor(true);
+      propertyTable.navigate(e);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Implementation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private String m_currentText;
+
+  /**
+   * Transfers data from {@link Property} to widget.
+   */
+  private void toWidget(Property property) throws Exception {
+    // prepare text
+    String text = getEditorText(property);
+    if (text == null) {
+      text = "";
+    }
+    // set text
+    m_currentText = text;
+    m_textControl.setText(text);
+    m_textControl.selectAll();
+  }
+
+  /**
+   * Transfers data from widget to {@link Property}.
+   *
+   * @return <code>true</code> if transfer was successful.
+   */
+  private boolean toProperty(Property property) throws Exception {
+    String text = m_textControl.getText();
+    // change property only if text was changed
+    if (!m_currentText.equals(text)) {
+      m_ignoreFocusLost = true;
+      try {
+        boolean success = setEditorText(property, text);
+        if (!success) {
+          return false;
+        }
+      } finally {
+        m_ignoreFocusLost = false;
+      }
+      // if value was successfully changed, update current text
+      m_currentText = text;
+    }
+    // OK, success
+    return true;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Operations
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return <code>true</code> if this editor can modify text.
+   */
+  protected boolean isEditable() {
+    return true;
+  }
+
+  /**
+   * @return the text to display in {@link Text} control.
+   */
+  protected abstract String getEditorText(Property property) throws Exception;
+
+  /**
+   * Modifies {@link Property} using given text.
+   *
+   * @return <code>true</code> if {@link Property} was successfully modified.
+   */
+  protected abstract boolean setEditorText(Property property, String text) throws Exception;
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanObjectPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanObjectPropertyEditor.java
new file mode 100644
index 0000000..4f80bb9
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanObjectPropertyEditor.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+import org.eclipse.wb.internal.core.utils.ui.DrawUtils;
+
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * The {@link PropertyEditor} for <code>Boolean</code>.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class BooleanObjectPropertyEditor extends PropertyEditor {
+  private static final Image m_nullImage = DesignerPlugin.getImage("properties/BooleanNull.png");
+  private static final Image m_trueImage = DesignerPlugin.getImage("properties/true.png");
+  private static final Image m_falseImage = DesignerPlugin.getImage("properties/false.png");
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final PropertyEditor INSTANCE = new BooleanObjectPropertyEditor();
+
+  private BooleanObjectPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public void paint(Property property, GC gc, int x, int y, int width, int height) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof Boolean) {
+      boolean booleanValue = ((Boolean) value).booleanValue();
+      Image image = booleanValue ? m_trueImage : m_falseImage;
+      String text = Boolean.toString(booleanValue);
+      paint(gc, x, y, width, height, text, image);
+    }
+    if (value == null) {
+      Image image = m_nullImage;
+      String text = "null";
+      paint(gc, x, y, width, height, text, image);
+    }
+  }
+
+  private void paint(GC gc, int x, int y, int width, int height, String text, Image image) {
+    // draw image
+    {
+      DrawUtils.drawImageCV(gc, image, x, y, height);
+      // prepare new position/width
+      int imageWidth = image.getBounds().width + 2;
+      x += imageWidth;
+      width -= imageWidth;
+    }
+    // draw text
+    {
+      DrawUtils.drawStringCV(gc, text, x, y, width, height);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public boolean activate(PropertyTable propertyTable, Property property, Point location)
+      throws Exception {
+    // check that user clicked on image
+    if (location == null || location.x < m_trueImage.getBounds().width + 2) {
+      invertValue(property);
+    }
+    // don't activate
+    return false;
+  }
+
+  @Override
+  public void doubleClick(Property property, Point location) throws Exception {
+    invertValue(property);
+  }
+
+  /**
+   * Inverts the value of given boolean {@link Property}.
+   */
+  private void invertValue(Property property) throws Exception {
+    Object value = property.getValue();
+    // null
+    if (value == null) {
+      property.setValue(true);
+      return;
+    }
+    // boolean
+    if (value instanceof Boolean) {
+      boolean booleanValue = ((Boolean) value).booleanValue();
+      property.setValue(!booleanValue);
+      return;
+    }
+    // unknown
+    property.setValue(true);
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanPropertyEditor.java
new file mode 100644
index 0000000..6639afe
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/BooleanPropertyEditor.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+import org.eclipse.wb.internal.core.utils.ui.DrawUtils;
+
+/**
+ * The {@link PropertyEditor} for <code>boolean</code>.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class BooleanPropertyEditor extends PropertyEditor {
+  private static final Image m_trueImage = DesignerPlugin.getImage("properties/true.png");
+  private static final Image m_falseImage = DesignerPlugin.getImage("properties/false.png");
+  private static final Image m_unknownImage =
+          DesignerPlugin.getImage("properties/BooleanUnknown.png");
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final PropertyEditor INSTANCE = new BooleanPropertyEditor();
+
+  private BooleanPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public void paint(Property property, GC gc, int x, int y, int width, int height) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof Boolean) {
+      boolean booleanValue = ((Boolean) value).booleanValue();
+      Image image = booleanValue ? m_trueImage : m_falseImage;
+      String text = Boolean.toString(booleanValue);
+      paint(gc, x, y, width, height, image, text);
+    } else {
+      paint(gc, x, y, width, height, m_unknownImage, "unknown");
+    }
+  }
+
+  /**
+   * Paints {@link Image} and text.
+   */
+  private void paint(GC gc, int x, int y, int width, int height, Image image, String text) {
+    // draw image
+    {
+      DrawUtils.drawImageCV(gc, image, x, y, height);
+      // prepare new position/width
+      int imageWidth = image.getBounds().width + 2;
+      x += imageWidth;
+      width -= imageWidth;
+    }
+    // draw text
+    DrawUtils.drawStringCV(gc, text, x, y, width, height);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public boolean activate(PropertyTable propertyTable, Property property, Point location)
+      throws Exception {
+    // check that user clicked on image
+    if (location == null || location.x < m_trueImage.getBounds().width + 2) {
+      invertValue(property);
+    }
+    // don't activate
+    return false;
+  }
+
+  @Override
+  public void doubleClick(Property property, Point location) throws Exception {
+    invertValue(property);
+  }
+
+  /**
+   * Inverts the value of given boolean {@link Property}.
+   */
+  private void invertValue(Property property) throws Exception {
+    // prepare current boolean value
+    boolean booleanValue = false;
+    {
+      Object value = property.getValue();
+      if (value instanceof Boolean) {
+        booleanValue = ((Boolean) value).booleanValue();
+      }
+    }
+    // set inverted value
+    property.setValue(!booleanValue);
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/CharacterPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/CharacterPropertyEditor.java
new file mode 100644
index 0000000..7e6cfe8
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/CharacterPropertyEditor.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.UiUtils;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link PropertyEditor} for {@link String}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class CharacterPropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final PropertyEditor INSTANCE = new CharacterPropertyEditor();
+
+  private CharacterPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof Character) {
+      return String.valueOf(((Character) value).charValue());
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    // check for delete
+    if (text.length() == 0) {
+      property.setValue(Property.UNKNOWN_VALUE);
+      return true;
+    }
+    // only one character
+    if (text.length() > 1) {
+      UiUtils.openWarning(
+          DesignerPlugin.getShell(),
+          property.getTitle(),
+          MessageFormat.format(ModelMessages.CharacterPropertyEditor_notValid, text));
+      return false;
+    }
+    // modify property
+    property.setValue(new Character(text.charAt(0)));
+    return true;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoubleObjectPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoubleObjectPropertyEditor.java
new file mode 100644
index 0000000..bb7dfc5
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoubleObjectPropertyEditor.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.UiUtils;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link PropertyEditor} for {@link Double}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class DoubleObjectPropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final DoubleObjectPropertyEditor INSTANCE = new DoubleObjectPropertyEditor();
+
+  private DoubleObjectPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value == null) {
+      return "null";
+    }
+    if (value instanceof Double) {
+      return value.toString();
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    text = text.trim();
+    // check for delete
+    if (text.length() == 0) {
+      property.setValue(Property.UNKNOWN_VALUE);
+      return true;
+    }
+    // check for "null"
+    if (text.equals("null")) {
+      property.setValue(null);
+      return true;
+    }
+    // prepare value
+    Double value;
+    try {
+      value = Double.valueOf(text);
+    } catch (Throwable e) {
+      UiUtils.openWarning(
+          DesignerPlugin.getShell(),
+          property.getTitle(),
+          MessageFormat.format(ModelMessages.DoubleObjectPropertyEditor_notValidDouble, text));
+      return false;
+    }
+    // modify property
+    property.setValue(value);
+    return true;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoublePropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoublePropertyEditor.java
new file mode 100644
index 0000000..40b73bf
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/DoublePropertyEditor.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.UiUtils;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link PropertyEditor} for <code>double</code>.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class DoublePropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final PropertyEditor INSTANCE = new DoublePropertyEditor();
+
+  private DoublePropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof Number) {
+      double doubleValue = ((Number) value).doubleValue();
+      return Double.toString(doubleValue);
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    text = text.trim();
+    // check for delete
+    if (text.length() == 0) {
+      property.setValue(Property.UNKNOWN_VALUE);
+    }
+    // prepare value
+    Double value;
+    try {
+      value = Double.valueOf(text);
+    } catch (Throwable e) {
+      UiUtils.openWarning(
+          DesignerPlugin.getShell(),
+          property.getTitle(),
+          MessageFormat.format(ModelMessages.DoublePropertyEditor_notValidDouble, text));
+      return false;
+    }
+    // modify property
+    property.setValue(value);
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/EnumerationValuesPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/EnumerationValuesPropertyEditor.java
new file mode 100644
index 0000000..a715df3
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/EnumerationValuesPropertyEditor.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import com.google.common.base.Objects;
+
+import org.eclipse.wb.core.controls.CCombo3;
+import org.eclipse.wb.internal.core.model.property.Property;
+
+import java.beans.PropertyDescriptor;
+
+/**
+ * {@link PropertyEditor} for "enumerationValues" attribute of {@link PropertyDescriptor}.
+ * <p>
+ * See http://javadude.com/articles/javabeanattributes.html for attributes.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public class EnumerationValuesPropertyEditor extends AbstractComboPropertyEditor
+    implements
+      IValueSourcePropertyEditor {
+  private final String[] m_names;
+  private final Object[] m_values;
+  private final String[] m_sources;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public EnumerationValuesPropertyEditor(Object attributeValue) {
+    Object[] enumElements = (Object[]) attributeValue;
+    int items = enumElements.length / 3;
+    m_names = new String[items];
+    m_values = new Object[items];
+    m_sources = new String[items];
+    for (int i = 0; i < items; i++) {
+      m_names[i] = (String) enumElements[3 * i + 0];
+      m_values[i] = enumElements[3 * i + 1];
+      m_sources[i] = (String) enumElements[3 * i + 2];
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // TextDisplayPropertyEditor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    // return name for value
+    if (value != Property.UNKNOWN_VALUE) {
+      for (int i = 0; i < m_values.length; i++) {
+        if (Objects.equal(m_values[i], value)) {
+          return m_names[i];
+        }
+      }
+    }
+    // unknown value
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // IValueSourcePropertyEditor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+public String getValueSource(Object value) throws Exception {
+    if (value != Property.UNKNOWN_VALUE) {
+      for (int i = 0; i < m_values.length; i++) {
+        if (Objects.equal(m_values[i], value)) {
+          return m_sources[i];
+        }
+      }
+    }
+    // unknown value
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // IClipboardSourceProvider
+  //
+  ////////////////////////////////////////////////////////////////////////////
+//  public String getClipboardSource(GenericProperty property) throws Exception {
+//    Object value = property.getValue();
+//    return getValueSource(value);
+//  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Combo
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected void addItems(Property property, CCombo3 combo) throws Exception {
+    for (String title : m_names) {
+      combo.add(title);
+    }
+  }
+
+  @Override
+  protected void selectItem(Property property, CCombo3 combo) throws Exception {
+    combo.setText(getText(property));
+  }
+
+  @Override
+  protected void toPropertyEx(Property property, CCombo3 combo, int index) throws Exception {
+    Object value = m_values[index];
+//    if (property instanceof GenericProperty) {
+//      GenericProperty genericProperty = (GenericProperty) property;
+//      String source = getValueSource(value);
+//      genericProperty.setExpression(source, value);
+//    } else {
+      property.setValue(value);
+//    }
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/FloatPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/FloatPropertyEditor.java
new file mode 100644
index 0000000..d4b468e
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/FloatPropertyEditor.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.UiUtils;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link PropertyEditor} for <code>float</code>.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public class FloatPropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final PropertyEditor INSTANCE = new FloatPropertyEditor();
+
+  protected FloatPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof Float) {
+      return value.toString();
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    text = text.trim();
+    // check for delete
+    if (text.length() == 0) {
+      property.setValue(Property.UNKNOWN_VALUE);
+    }
+    // prepare value
+    Float value;
+    try {
+      value = Float.valueOf(text);
+    } catch (Throwable e) {
+      UiUtils.openWarning(
+          DesignerPlugin.getShell(),
+          property.getTitle(),
+          MessageFormat.format(ModelMessages.FloatPropertyEditor_notValidFloat, text));
+      return false;
+    }
+    // modify property
+    property.setValue(value);
+    return true;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ITextValuePropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ITextValuePropertyEditor.java
new file mode 100644
index 0000000..ed38c04
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ITextValuePropertyEditor.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+
+/**
+ * Extension of {@link PropertyEditor} that can set value using its text presentation.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public interface ITextValuePropertyEditor {
+  /**
+   * Sets value that corresponds given text.
+   */
+  void setText(Property property, String text) throws Exception;
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IValueSourcePropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IValueSourcePropertyEditor.java
new file mode 100644
index 0000000..2999bf1
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IValueSourcePropertyEditor.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+/**
+ * Extension for {@link PropertyEditor} that can be used to convert {@link Object} value into Java
+ * source.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public interface IValueSourcePropertyEditor {
+  /**
+   * @return the Java source for given value.
+   */
+  String getValueSource(Object value) throws Exception;
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerObjectPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerObjectPropertyEditor.java
new file mode 100644
index 0000000..f488dff
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerObjectPropertyEditor.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.UiUtils;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link PropertyEditor} for {@link Integer}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class IntegerObjectPropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final IntegerObjectPropertyEditor INSTANCE = new IntegerObjectPropertyEditor();
+
+  private IntegerObjectPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value == null) {
+      return "null";
+    }
+    if (value instanceof Integer) {
+      return value.toString();
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    text = text.trim();
+    // check for delete
+    if (text.length() == 0) {
+      property.setValue(Property.UNKNOWN_VALUE);
+      return true;
+    }
+    // check for "null"
+    if (text.equals("null")) {
+      property.setValue(null);
+      return true;
+    }
+    // prepare value
+    Integer value;
+    try {
+      value = Integer.valueOf(text);
+    } catch (Throwable e) {
+      UiUtils.openWarning(
+          DesignerPlugin.getShell(),
+          property.getTitle(),
+          MessageFormat.format(ModelMessages.IntegerObjectPropertyEditor_notValidInt, text));
+      return false;
+    }
+    // modify property
+    property.setValue(value);
+    return true;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerPropertyEditor.java
new file mode 100644
index 0000000..5be13da
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/IntegerPropertyEditor.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.UiUtils;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link PropertyEditor} for <code>int</code>.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class IntegerPropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final IntegerPropertyEditor INSTANCE = new IntegerPropertyEditor();
+
+  private IntegerPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof Integer) {
+      return value.toString();
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    text = text.trim();
+    // check for delete
+    if (text.length() == 0) {
+      property.setValue(Property.UNKNOWN_VALUE);
+      return true;
+    }
+    // prepare value
+    Integer value;
+    try {
+      value = Integer.valueOf(text);
+    } catch (Throwable e) {
+      UiUtils.openWarning(
+          DesignerPlugin.getShell(),
+          property.getTitle(),
+          MessageFormat.format(ModelMessages.IntegerPropertyEditor_notValidInt, text));
+      return false;
+    }
+    // modify property
+    property.setValue(value);
+    return true;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LocalePropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LocalePropertyEditor.java
new file mode 100644
index 0000000..9a6563d
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LocalePropertyEditor.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+
+import java.util.Locale;
+
+/**
+ * {@link PropertyEditor} for {@link Locale}.
+ *
+ * @author sablin_aa
+ * @coverage core.model.property.editor
+ */
+public final class LocalePropertyEditor extends TextDialogPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final PropertyEditor INSTANCE = new LocalePropertyEditor();
+
+  private LocalePropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof Locale) {
+      return ((Locale) value).getDisplayName();
+    }
+    // unknown value
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected void openDialog(Property property) throws Exception {
+    Object value = property.getValue();
+//    ChooseLocaleDialog localeDialog;
+//    if (value instanceof Locale) {
+//      localeDialog = new ChooseLocaleDialog(DesignerPlugin.getShell(), (Locale) value);
+//    } else {
+//      localeDialog = new ChooseLocaleDialog(DesignerPlugin.getShell(), null);
+//    }
+//    // open dialog
+//    if (localeDialog.open() == Window.OK) {
+//      property.setValue(localeDialog.getSelectedLocale().getLocale());
+//    }
+    System.out.println("TODO: Custom locale chooser here");
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongObjectPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongObjectPropertyEditor.java
new file mode 100644
index 0000000..7a74ded
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongObjectPropertyEditor.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.UiUtils;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link PropertyEditor} for {@link Long}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class LongObjectPropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final LongObjectPropertyEditor INSTANCE = new LongObjectPropertyEditor();
+
+  private LongObjectPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value == null) {
+      return "null";
+    }
+    if (value instanceof Long) {
+      return value.toString();
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    text = text.trim();
+    // check for delete
+    if (text.length() == 0) {
+      property.setValue(Property.UNKNOWN_VALUE);
+      return true;
+    }
+    // check for "null"
+    if (text.equals("null")) {
+      property.setValue(null);
+      return true;
+    }
+    // prepare value
+    Long value;
+    try {
+      value = Long.valueOf(text);
+    } catch (Throwable e) {
+      UiUtils.openWarning(
+          DesignerPlugin.getShell(),
+          property.getTitle(),
+          MessageFormat.format(ModelMessages.LongObjectPropertyEditor_notValidLong, text));
+      return false;
+    }
+    // modify property
+    property.setValue(value);
+    return true;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongPropertyEditor.java
new file mode 100644
index 0000000..8c17f83
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/LongPropertyEditor.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.UiUtils;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link PropertyEditor} for <code>long</code>.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public class LongPropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final PropertyEditor INSTANCE = new LongPropertyEditor();
+
+  private LongPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof Long) {
+      return value.toString();
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    text = text.trim();
+    // check for delete
+    if (text.length() == 0) {
+      property.setValue(Property.UNKNOWN_VALUE);
+    }
+    // prepare value
+    Long value;
+    try {
+      value = Long.valueOf(text);
+    } catch (Throwable e) {
+      UiUtils.openWarning(
+          DesignerPlugin.getShell(),
+          property.getTitle(),
+          MessageFormat.format(ModelMessages.LongPropertyEditor_notValidLong, text));
+      return false;
+    }
+    // modify property
+    property.setValue(value);
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyDescriptorEditorProvider.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyDescriptorEditorProvider.java
new file mode 100644
index 0000000..a60b698
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyDescriptorEditorProvider.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import java.beans.PropertyDescriptor;
+
+/**
+ * {@link PropertyEditorProvider} that creates editors based on {@link PropertyDescriptor}
+ * attributes, such as "enumerationValues".
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class PropertyDescriptorEditorProvider extends PropertyEditorProvider {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // PropertyEditorProvider
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public PropertyEditor getEditorForPropertyDescriptor(PropertyDescriptor descriptor)
+      throws Exception {
+    {
+      Object attributeValue = descriptor.getValue("enumerationValues");
+      if (isEnumerationProperty(descriptor)) {
+        return new EnumerationValuesPropertyEditor(attributeValue);
+      }
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return <code>true</code> if given {@link PropertyDescriptor} has attribute "enumerationValues"
+   *         with valid value structure.
+   */
+  private static boolean isEnumerationProperty(PropertyDescriptor descriptor) {
+    Object attributeValue = descriptor.getValue("enumerationValues");
+    // should be Object[]
+    if (!(attributeValue instanceof Object[])) {
+      return false;
+    }
+    Object[] enumElements = (Object[]) attributeValue;
+    // should be multiple 3
+    if (enumElements.length % 3 != 0) {
+      return false;
+    }
+    // elements should be sequence of [String,Object,String]
+    for (int i = 0; i < enumElements.length; i++) {
+      Object element = enumElements[i];
+      if (i % 3 == 0 && !(element instanceof String)) {
+        return false;
+      }
+      if (i % 3 == 2 && !(element instanceof String)) {
+        return false;
+      }
+    }
+    // OK
+    return true;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditor.java
new file mode 100644
index 0000000..fd2fa8f
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditor.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+/**
+ * Abstract editor for {@link Property}.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public abstract class PropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the instance of {@link PropertyEditorPresentation}.
+   */
+  public PropertyEditorPresentation getPresentation() {
+    return null;
+  }
+
+  /**
+   * Paints given {@link Property} given rectangle <code>(x, y, width, height)</code> of {@link GC}.
+   */
+  public abstract void paint(Property property, GC gc, int x, int y, int width, int height)
+      throws Exception;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Activates editor for given {@link Property} at given place of {@link Composite}. Activation
+   * happens when user selects property in {@link PropertyTable}. {@link PropertyEditor} should
+   * create here any {@link Control}'s required to edit {@link Property}.
+   *
+   * If any exception happens, {@link PropertyEditor} will be deactivated.
+   *
+   * @param location
+   *          the mouse location, if editor is activated using mouse click, or <code>null</code> if
+   *          it is activated using keyboard.
+   *
+   * @return <code>true</code> if editor should be remembered as active for future
+   *         {@link #setBounds(Rectangle)} and {@link #deactivate(boolean)} invocation. Some editors
+   *         need such local activation (for example for String), some - not (for boolean).
+   */
+  public boolean activate(PropertyTable propertyTable, Property property, Point location)
+      throws Exception {
+    return false;
+  }
+
+  /**
+   * Sets the new bounds for editor's control.
+   */
+  public void setBounds(Rectangle bounds) {
+  }
+
+  /**
+   * Deactivates editor for current {@link Property}. {@link PropertyEditor} should dispose any
+   * {@link Control}'s created before in {@link #activate(PropertyTable, Property, Point)}.
+   *
+   * If any exception happened during activation, editor still should be able to deactivate
+   * correctly.
+   *
+   * @param save
+   *          is <code>true</code> if property should save value to {@link Property}.
+   */
+  public void deactivate(PropertyTable propertyTable, Property property, boolean save) {
+  }
+
+  /**
+   * Handles double click on {@link Property} value in {@link PropertyTable}.
+   *
+   * @param location
+   *          the mouse location, relative to editor
+   */
+  public void doubleClick(Property property, Point location) throws Exception {
+  }
+
+  /**
+   * Handles {@link SWT#KeyDown} event in {@link PropertyTable}.
+   */
+  public void keyDown(PropertyTable propertyTable, Property property, KeyEvent event)
+      throws Exception {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // IAdaptable
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public <T> T getAdapter(Class<T> adapter) {
+    return null;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditorProvider.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditorProvider.java
new file mode 100644
index 0000000..2d11cbc
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/PropertyEditorProvider.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import java.beans.PropertyDescriptor;
+
+/**
+ * Provider for creating {@link PropertyEditor}'s.
+ * 
+ * @author lobas_av
+ * @coverage core.model.property.editor
+ */
+public class PropertyEditorProvider {
+  /**
+   * @return the {@link PropertyEditor} for given property type or <code>null</code>.
+   */
+  public PropertyEditor getEditorForType(Class<?> propertyType) throws Exception {
+    return null;
+  }
+
+  /**
+   * @return the {@link PropertyEditor} for given {@link java.beans.PropertyEditor} editor type or
+   *         <code>null</code>.
+   */
+  public PropertyEditor getEditorForEditorType(Class<?> editorType) throws Exception {
+    return null;
+  }
+
+  /**
+   * @return the {@link PropertyEditor} for given {@link PropertyDescriptor} or <code>null</code>.
+   */
+  public PropertyEditor getEditorForPropertyDescriptor(PropertyDescriptor descriptor)
+      throws Exception {
+    return null;
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortObjectPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortObjectPropertyEditor.java
new file mode 100644
index 0000000..c1f8383
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortObjectPropertyEditor.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.UiUtils;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link PropertyEditor} for {@link Short}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class ShortObjectPropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final ShortObjectPropertyEditor INSTANCE = new ShortObjectPropertyEditor();
+
+  private ShortObjectPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value == null) {
+      return "null";
+    }
+    if (value instanceof Short) {
+      return value.toString();
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    text = text.trim();
+    // check for delete
+    if (text.length() == 0) {
+      property.setValue(Property.UNKNOWN_VALUE);
+      return true;
+    }
+    // check for "null"
+    if (text.equals("null")) {
+      property.setValue(null);
+      return true;
+    }
+    // prepare value
+    Short value;
+    try {
+      value = Short.valueOf(text);
+    } catch (Throwable e) {
+      UiUtils.openWarning(
+          DesignerPlugin.getShell(),
+          property.getTitle(),
+          MessageFormat.format(ModelMessages.ShortObjectPropertyEditor_notValidShort, text));
+      return false;
+    }
+    // modify property
+    property.setValue(value);
+    return true;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortPropertyEditor.java
new file mode 100644
index 0000000..dba61c9
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/ShortPropertyEditor.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.UiUtils;
+
+import java.text.MessageFormat;
+
+/**
+ * The {@link PropertyEditor} for <code>short</code>.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public class ShortPropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final PropertyEditor INSTANCE = new ShortPropertyEditor();
+
+  private ShortPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof Short) {
+      return value.toString();
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    text = text.trim();
+    // check for delete
+    if (text.length() == 0) {
+      property.setValue(Property.UNKNOWN_VALUE);
+    }
+    // prepare value
+    Short value;
+    try {
+      value = Short.valueOf(text);
+    } catch (Throwable e) {
+      UiUtils.openWarning(
+          DesignerPlugin.getShell(),
+          property.getTitle(),
+          MessageFormat.format(ModelMessages.ShortPropertyEditor_notValidShort, text));
+      return false;
+    }
+    // modify property
+    property.setValue(value);
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringArrayPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringArrayPropertyEditor.java
new file mode 100644
index 0000000..fd78e01
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringArrayPropertyEditor.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import com.google.common.base.Joiner;
+
+import org.eclipse.jface.window.Window;
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.dialogs.StringsDialog;
+
+/**
+ * {@link PropertyEditor} for array of {@link String}'s.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public final class StringArrayPropertyEditor extends TextDialogPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final PropertyEditor INSTANCE = new StringArrayPropertyEditor();
+
+  private StringArrayPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getText(Property property) throws Exception {
+    String[] items = getItems(property);
+    return "[" + Joiner.on(", ").join(items) + "]";
+  }
+
+  /**
+   * @return the items specified in value of given {@link Property}.
+   */
+  private static String[] getItems(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof String[]) {
+      return (String[]) value;
+    }
+    // no items
+    return new String[0];
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected void openDialog(Property property) throws Exception {
+    StringsDialog itemsDialog =
+        new StringsDialog(DesignerPlugin.getShell(),
+            DesignerPlugin.getDefault(),
+            property.getTitle(),
+            ModelMessages.StringArrayPropertyEditor_itemsLabel,
+            ModelMessages.StringArrayPropertyEditor_hint);
+    itemsDialog.setItems(getItems(property));
+    // open dialog
+    if (itemsDialog.open() == Window.OK) {
+      property.setValue(itemsDialog.getItems());
+    }
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringComboPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringComboPropertyEditor.java
new file mode 100644
index 0000000..1861475
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringComboPropertyEditor.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.core.controls.CCombo3;
+import org.eclipse.wb.internal.core.model.property.Property;
+
+/**
+ * The {@link PropertyEditor} for selecting single {@link String} value from given array.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public class StringComboPropertyEditor extends AbstractComboPropertyEditor {
+  private final String[] m_items;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public StringComboPropertyEditor(String... items) {
+    m_items = items;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getText(Property property) throws Exception {
+    return (String) property.getValue();
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // AbstractComboPropertyEditor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected void addItems(Property property, CCombo3 combo) throws Exception {
+    for (String item : m_items) {
+      combo.add(item);
+    }
+  }
+
+  @Override
+  protected void selectItem(Property property, CCombo3 combo) throws Exception {
+    combo.setText(getText(property));
+  }
+
+  @Override
+  protected void toPropertyEx(Property property, CCombo3 combo, int index) throws Exception {
+    property.setValue(m_items[index]);
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringListPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringListPropertyEditor.java
new file mode 100644
index 0000000..1bb8cd8
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/StringListPropertyEditor.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.core.controls.CCombo3;
+import org.eclipse.wb.internal.core.model.property.Property;
+
+/**
+ * The {@link PropertyEditor} for selecting single string from given set.
+ *
+ * @author sablin_aa
+ * @coverage core.model.property.editor
+ */
+public final class StringListPropertyEditor extends AbstractListPropertyEditor {
+  private boolean m_ignoreCase;
+  private String[] m_strings;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Combo
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected void toPropertyEx_simpleProperty(Property property, CCombo3 combo, int index)
+      throws Exception {
+    property.setValue(m_strings[index]);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access to list items
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected int getCount() {
+    return m_strings.length;
+  }
+
+  @Override
+  protected int getValueIndex(Object value) {
+    if (value instanceof String) {
+      String string = (String) value;
+      for (int i = 0; i < getCount(); i++) {
+        if (m_ignoreCase) {
+          if (string.equalsIgnoreCase(m_strings[i])) {
+            return i;
+          }
+        } else {
+          if (string.equals(m_strings[i])) {
+            return i;
+          }
+        }
+      }
+    }
+    return -1;
+  }
+
+  @Override
+  protected String getTitle(int index) {
+    return m_strings[index];
+  }
+
+  @Override
+  protected String getExpression(int index) throws Exception {
+    //return StringConverter.INSTANCE.toJavaSource(null, m_strings[index]);
+      // HACK!!
+      System.out.println("HACK!");
+      return m_strings[index];
+  }
+//
+//  ////////////////////////////////////////////////////////////////////////////
+//  //
+//  // IConfigurablePropertyObject
+//  //
+//  ////////////////////////////////////////////////////////////////////////////
+//  public void configure(EditorState state, Map<String, Object> parameters) throws Exception {
+//    m_strings = getParameterAsArray(parameters, "strings");
+//    m_ignoreCase = "true".equals(parameters.get("ignoreCase"));
+//  }
+//
+//  /**
+//   * Configures this editor externally.
+//   */
+//  public void configure(String[] strings) {
+//    m_strings = strings;
+//  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextControlActionsManager.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextControlActionsManager.java
new file mode 100644
index 0000000..dc7ba74
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextControlActionsManager.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.utils.binding.editors.controls.DefaultControlActionsManager;
+
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Manager for installing/unistalling global handlers for {@link Text} actions commands.
+ * 
+ * @author mitin_aa
+ * @author sablin_aa
+ * @coverage core.model.property.editor
+ */
+public final class TextControlActionsManager extends DefaultControlActionsManager {
+  private final Text m_text;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public TextControlActionsManager(final Text text) {
+    super(text);
+    m_text = text;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Handlers
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected void selectAllExecuted() {
+    m_text.selectAll();
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDialogPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDialogPropertyEditor.java
new file mode 100644
index 0000000..885d4ef
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDialogPropertyEditor.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation;
+import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Abstract {@link PropertyEditor} that displays text and button to open dialog.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public abstract class TextDialogPropertyEditor extends TextDisplayPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private final PropertyEditorPresentation m_presentation = new ButtonPropertyEditorPresentation() {
+    @Override
+    protected void onClick(PropertyTable propertyTable, Property property) throws Exception {
+      openDialog(property);
+    }
+  };
+
+  @Override
+  public final PropertyEditorPresentation getPresentation() {
+    return m_presentation;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public boolean activate(PropertyTable propertyTable, Property property, Point location)
+      throws Exception {
+    // activate using keyboard
+    if (location == null) {
+      openDialog(property);
+    }
+    // don't activate
+    return false;
+  }
+
+  /**
+   * Opens editing dialog.
+   */
+  protected abstract void openDialog(Property property) throws Exception;
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDisplayPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDisplayPropertyEditor.java
new file mode 100644
index 0000000..9cf3703
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/TextDisplayPropertyEditor.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor;
+
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTooltipProvider;
+import org.eclipse.wb.internal.core.utils.ui.DrawUtils;
+
+/**
+ * Abstract {@link PropertyEditor} for displaying text as {@link Property} value in
+ * {@link PropertyTable}.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public abstract class TextDisplayPropertyEditor extends PropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public void paint(Property property, GC gc, int x, int y, int width, int height) throws Exception {
+    String text = getText(property);
+    if (text != null) {
+      DrawUtils.drawStringCV(gc, text, x, y, width, height);
+    }
+  }
+
+  /**
+   * @return the text for displaying value of given {@link Property} or <code>null</code> if value
+   *         of {@link Property} is unknown.
+   */
+  protected abstract String getText(Property property) throws Exception;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // IAdaptable
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public <T> T getAdapter(Class<T> adapter) {
+    // tooltip for value text
+    if (adapter == PropertyTooltipProvider.class) {
+      return adapter.cast(createPropertyTooltipProvider());
+    }
+    return super.getAdapter(adapter);
+  }
+
+  /**
+   * @return the {@link PropertyTooltipProvider} to display value tooltip.
+   */
+  protected PropertyTooltipProvider createPropertyTooltipProvider() {
+    return null;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/complex/IComplexPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/complex/IComplexPropertyEditor.java
new file mode 100644
index 0000000..0634cfe
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/complex/IComplexPropertyEditor.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor.complex;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
+
+/**
+ * Extension for {@link PropertyEditor} that specifies that it has sub-properties.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public interface IComplexPropertyEditor {
+  /**
+   * @return sub-properties of given complex property.
+   */
+  Property[] getProperties(Property property) throws Exception;
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentation.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentation.java
new file mode 100644
index 0000000..e33970d
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentation.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor.presentation;
+
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.EnvironmentUtils;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Button;
+
+/**
+ * Implementation of {@link PropertyEditorPresentation} for displaying {@link Button}.
+ * 
+ * @author scheglov_ke
+ * @author mitin_aa
+ * @coverage core.model.property.editor
+ */
+public abstract class ButtonPropertyEditorPresentation extends PropertyEditorPresentation {
+  private final int m_style;
+  private final ButtonPropertyEditorPresentationImpl m_impl;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructors
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public ButtonPropertyEditorPresentation() {
+    this(SWT.NONE);
+  }
+
+  public ButtonPropertyEditorPresentation(int style) {
+    m_style = style;
+    m_impl =
+        EnvironmentUtils.IS_MAC
+            ? new ButtonPropertyEditorPresentationImplMac(this)
+            : new ButtonPropertyEditorPresentationImpl(this);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets "selection" property of {@link Button}.
+   */
+  public final void setSelection(PropertyTable propertyTable, Property property, boolean selected) {
+    m_impl.setSelection(propertyTable, property, selected);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // PropertyEditorPresentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public final int show(final PropertyTable propertyTable,
+      final Property property,
+      final int x,
+      final int y,
+      final int width,
+      final int height) {
+    return m_impl.show(propertyTable, property, x, y, width, height);
+  }
+
+  @Override
+  public final void hide(PropertyTable propertyTable, Property property) {
+    m_impl.hide(propertyTable, property);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  final int getStyle() {
+    return m_style;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Implementation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the {@link Image} to display on {@link Button}.
+   */
+  protected Image getImage() {
+    return DesignerPlugin.getImage("properties/dots.gif");
+  }
+
+  /**
+   * @return the tooltip text to display for {@link Button}.
+   */
+  protected String getTooltip() {
+    return null;
+  }
+
+  /**
+   * Handles click on {@link Button}.
+   */
+  protected abstract void onClick(PropertyTable propertyTable, Property property) throws Exception;
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImpl.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImpl.java
new file mode 100644
index 0000000..209bf39
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImpl.java
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor.presentation;
+
+import com.google.common.collect.Maps;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+import org.eclipse.wb.internal.core.utils.Pair;
+
+import java.util.Map;
+
+/**
+ * Internal implementation of {@link PropertyEditorPresentation} for displaying {@link Button}.
+ *
+ * @author scheglov_ke
+ * @author mitin_aa
+ * @coverage core.model.property.editor
+ */
+class ButtonPropertyEditorPresentationImpl extends PropertyEditorPresentation {
+  protected final PropertyToControlMap m_propertyToControl = new PropertyToControlMap();
+  private final ButtonPropertyEditorPresentation m_presentation;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public ButtonPropertyEditorPresentationImpl(ButtonPropertyEditorPresentation presentation) {
+    m_presentation = presentation;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // PropertyEditorPresentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public final void hide(PropertyTable propertyTable, Property property) {
+    Control control = m_propertyToControl.remove(propertyTable, property);
+    if (control != null) {
+      control.dispose();
+    }
+  }
+
+  @Override
+  public final int show(PropertyTable propertyTable,
+      Property property,
+      int x,
+      int y,
+      int width,
+      int height) {
+    // prepare control
+    Control control = m_propertyToControl.get(propertyTable, property);
+    if (control == null) {
+      control = createControl(propertyTable, property);
+    }
+    // set bounds
+    final int controlWidth = height;
+    final int controlX = x + width - controlWidth;
+    setBounds(control, controlX, y, controlWidth, height);
+    return controlWidth;
+  }
+
+  /**
+   * Finds and select the appropriate {@link Control} belonging to given property.
+   */
+  public void setSelection(PropertyTable propertyTable, Property property, boolean selected) {
+    Button button = (Button) m_propertyToControl.get(propertyTable, property);
+    if (button != null) {
+      button.setSelection(selected);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Control
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Creates the control for given property and initializes newly created control.
+   */
+  private Control createControl(final PropertyTable propertyTable, final Property property) {
+    Control control = createControlImpl(propertyTable, property);
+    m_propertyToControl.put(propertyTable, property, control);
+    // when Control disposed, remove Control/Property from map to avoid memory leak
+    control.addListener(SWT.Dispose, new Listener() {
+      @Override
+    public void handleEvent(Event e) {
+        m_propertyToControl.remove(propertyTable, property);
+      }
+    });
+    // activate property on mouse down
+    control.addListener(SWT.MouseDown, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        propertyTable.deactivateEditor(true);
+        propertyTable.setActiveProperty(property);
+      }
+    });
+    // return focus on propertyTable after click
+    control.addListener(SWT.MouseUp, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        propertyTable.forceFocus();
+      }
+    });
+    // handle selection
+    control.addListener(SWT.Selection, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        try {
+          getPresentation().onClick(propertyTable, property);
+        } catch (Throwable e) {
+          propertyTable.deactivateEditor(false);
+          propertyTable.handleException(e);
+        }
+      }
+    });
+    return control;
+  }
+
+  /**
+   * Creates the {@link Control} instance. By default, {@link Button} instance created.
+   */
+  protected Control createControlImpl(final PropertyTable propertyTable, final Property property) {
+    Button button = new Button(propertyTable, getPresentation().getStyle());
+    button.setImage(getPresentation().getImage());
+    button.setToolTipText(getPresentation().getTooltip());
+    return button;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the 'parent' presentation. Internal usage only.
+   */
+  protected final ButtonPropertyEditorPresentation getPresentation() {
+    return m_presentation;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets new bounds for {@link Control}, optimizing when possible.
+   */
+  private static void setBounds(Control control, int newX, int newY, int newWidth, int newHeight) {
+    // check, may be Control is invisible, so no reason to change bounds
+    {
+      // is in negative zone
+      if (newY + newHeight < 0) {
+        control.setVisible(false);
+        return;
+      }
+      // is out of client area height
+      Rectangle parentArea = control.getParent().getClientArea();
+      if (newY > parentArea.height) {
+        control.setVisible(false);
+        return;
+      }
+    }
+    // well, now we sure that Control is visible
+    if (!control.getVisible()) {
+      control.setVisible(true);
+    }
+    // prepare old size, remember new
+    Integer oldWidthObject = (Integer) control.getData("oldWidth");
+    Integer oldHeightObject = (Integer) control.getData("oldHeight");
+    control.setData("oldWidth", newWidth);
+    control.setData("oldHeight", newHeight);
+    // check, may be same size
+    if (oldWidthObject != null) {
+      int oldWidth = oldWidthObject.intValue();
+      int oldHeight = oldHeightObject.intValue();
+      if (oldWidth == newWidth && oldHeight == newHeight) {
+        control.setLocation(newX, newY);
+        return;
+      }
+    }
+    // no any optimization possible, just set bounds
+    control.setBounds(newX, newY, newWidth, newHeight);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Controls map
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected static final class PropertyToControlMap {
+    private final Map<Pair<PropertyTable, Property>, Control> m_map = Maps.newHashMap();
+
+    void put(PropertyTable propertyTable, Property property, Control control) {
+      m_map.put(Pair.create(propertyTable, property), control);
+    }
+
+    Control remove(PropertyTable propertyTable, Property property) {
+      return m_map.remove(Pair.create(propertyTable, property));
+    }
+
+    Control get(PropertyTable propertyTable, Property property) {
+      return m_map.get(Pair.create(propertyTable, property));
+    }
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImplMac.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImplMac.java
new file mode 100644
index 0000000..d61a606
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/ButtonPropertyEditorPresentationImplMac.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor.presentation;
+
+import org.eclipse.wb.core.controls.CFlatButton;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * Internal implementation of {@link PropertyEditorPresentation} for displaying special owner-draw
+ * button for Mac OSX.
+ * 
+ * @author mitin_aa
+ * @coverage core.model.property.editor
+ */
+final class ButtonPropertyEditorPresentationImplMac extends ButtonPropertyEditorPresentationImpl {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public ButtonPropertyEditorPresentationImplMac(ButtonPropertyEditorPresentation presentation) {
+    super(presentation);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Control
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected final Control createControlImpl(final PropertyTable propertyTable, Property property) {
+    CFlatButton button = new CFlatButton(propertyTable, SWT.NONE);
+    button.setImage(getPresentation().getImage());
+    button.setToolTipText(getPresentation().getTooltip());
+    return button;
+  }
+
+  @Override
+  public final void setSelection(PropertyTable propertyTable, Property property, boolean selected) {
+    CFlatButton button = (CFlatButton) m_propertyToControl.get(propertyTable, property);
+    if (button != null) {
+      button.setSelected(selected);
+    }
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/CompoundPropertyEditorPresentation.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/CompoundPropertyEditorPresentation.java
new file mode 100644
index 0000000..39bfa7a
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/CompoundPropertyEditorPresentation.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor.presentation;
+
+import com.google.common.collect.Lists;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+import java.util.List;
+
+/**
+ * Implementation of {@link PropertyEditorPresentation} that contains zero or more other
+ * {@link PropertyEditorPresentation}'s.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public class CompoundPropertyEditorPresentation extends PropertyEditorPresentation {
+  private final List<PropertyEditorPresentation> m_presentations = Lists.newArrayList();
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Adds child {@link PropertyEditorPresentation}.<br>
+   * Child {@link PropertyEditorPresentation}'s are displayed from right to left.
+   */
+  public void add(PropertyEditorPresentation presentation) {
+    m_presentations.add(presentation);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // PropertyEditorPresentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public int show(PropertyTable propertyTable,
+      Property property,
+      int x,
+      int y,
+      int width,
+      int height) {
+    int sumWidth = 0;
+    for (PropertyEditorPresentation presentation : m_presentations) {
+      int presentationWidth = presentation.show(propertyTable, property, x, y, width, height);
+      sumWidth += presentationWidth;
+      width -= presentationWidth;
+    }
+    return sumWidth;
+  }
+
+  @Override
+  public void hide(PropertyTable propertyTable, Property property) {
+    for (PropertyEditorPresentation presentation : m_presentations) {
+      presentation.hide(propertyTable, property);
+    }
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/PropertyEditorPresentation.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/PropertyEditorPresentation.java
new file mode 100644
index 0000000..843338e
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/presentation/PropertyEditorPresentation.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor.presentation;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+/**
+ * Implementations of {@link PropertyEditorPresentation} are used to show some presentation for
+ * visible, but not activated yet {@link PropertyEditor}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public abstract class PropertyEditorPresentation {
+  /**
+   * Shows presentation for given {@link Property}.
+   * 
+   * @return the width that this presentation occupies on the right of given rectangle.
+   */
+  public abstract int show(PropertyTable propertyTable,
+      Property property,
+      int x,
+      int y,
+      int width,
+      int height);
+
+  /**
+   * Hides presentation.
+   */
+  public abstract void hide(PropertyTable propertyTable, Property property);
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyDialog.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyDialog.java
new file mode 100644
index 0000000..2b0d47e
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyDialog.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor.string;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.ModelMessages;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
+import org.eclipse.wb.internal.core.utils.execution.RunnableEx;
+import org.eclipse.wb.internal.core.utils.ui.GridDataFactory;
+import org.eclipse.wb.internal.core.utils.ui.dialogs.ResizableDialog;
+
+/**
+ * {@link Dialog} for editing value in {@link StringPropertyEditor}.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public class StringPropertyDialog extends ResizableDialog {
+    // NOTE: In WindowBuilder this class had a lot of support for
+    // editing Java strings, dealing with automatic localization
+    // etc. This will need to be done differently in ADT (and had hooks
+    // into a bunch of other parts of WindowBuilder we're not including)
+    // so this was all stripped down to a plain String editor.
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Final fields
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private final Property m_property;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public StringPropertyDialog(Shell parentShell, Property property) throws Exception {
+    super(parentShell, DesignerPlugin.getDefault());
+    m_property = property;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // GUI
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private Text m_valueText;
+
+  @Override
+  public void create() {
+    super.create();
+    m_valueText.selectAll();
+  }
+
+  @Override
+  protected Control createDialogArea(Composite parent) {
+    Composite area = (Composite) super.createDialogArea(parent);
+    // value
+    {
+      // BEGIN ADT MODIFICATIONS
+      if (isMultiLine()) {
+      // END ADT MODIFICATIONS
+        m_valueText = new Text(area, SWT.BORDER | SWT.MULTI | SWT.WRAP);
+        GridDataFactory.create(m_valueText).grab().hintC(80, 8).fill();
+      // BEGIN ADT MODIFICATIONS
+      } else {
+        m_valueText = new Text(area, SWT.BORDER | SWT.SINGLE);
+        GridDataFactory.create(m_valueText).grab().hintC(50, 1).fill();
+      }
+      // END ADT MODIFICATIONS
+      // initial value
+      ExecutionUtils.runLog(new RunnableEx() {
+        @Override
+        public void run() throws Exception {
+          Object value = m_property.getValue();
+          if (value instanceof String) {
+            m_valueText.setText((String) value);
+          }
+        }
+      });
+      // handle Ctrl+Enter as OK
+      m_valueText.addKeyListener(new KeyAdapter() {
+        @Override
+        public void keyPressed(KeyEvent e) {
+          if (e.stateMask == SWT.CTRL && e.keyCode == SWT.CR) {
+            okPressed();
+          }
+        }
+      });
+    }
+
+    return area;
+  }
+
+  // BEGIN ADT MODIFICATIONS
+  protected boolean isMultiLine() {
+      return true;
+  }
+  // END ADT MODIFICATIONS
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Shell
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected void configureShell(Shell newShell) {
+    super.configureShell(newShell);
+    newShell.setText(ModelMessages.StringPropertyDialog_title);
+  }
+
+  @Override
+  protected void okPressed() {
+    final String value = m_valueText.getText();
+    ExecutionUtils.runLog(new RunnableEx() {
+        @Override
+        public void run() throws Exception {
+          m_property.setValue(value);
+        }
+    });
+    // close dialog
+    super.okPressed();
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyEditor.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyEditor.java
new file mode 100644
index 0000000..0202fe5
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/string/StringPropertyEditor.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.editor.string;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.editor.AbstractTextPropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation;
+import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+import org.eclipse.jface.window.Window;
+
+/**
+ * The {@link PropertyEditor} for {@link String}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.editor
+ */
+public class StringPropertyEditor extends AbstractTextPropertyEditor {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static final PropertyEditor INSTANCE = new StringPropertyEditor();
+
+  private StringPropertyEditor() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private final PropertyEditorPresentation m_presentation = new ButtonPropertyEditorPresentation() {
+    @Override
+    protected void onClick(PropertyTable propertyTable, Property property) throws Exception {
+      openDialog(propertyTable, property);
+    }
+  };
+
+  @Override
+  public PropertyEditorPresentation getPresentation() {
+    return m_presentation;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Presentation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public String getText(Property property) throws Exception {
+    Object value = property.getValue();
+    if (value instanceof String) {
+      return (String) value;
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected String getEditorText(Property property) throws Exception {
+    return getText(property);
+  }
+
+  @Override
+  protected boolean setEditorText(Property property, String text) throws Exception {
+    property.setValue(text);
+    return true;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editing in dialog
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Opens editing dialog.
+   */
+  private void openDialog(PropertyTable propertyTable, Property property) throws Exception {
+    StringPropertyDialog dialog = new StringPropertyDialog(propertyTable.getShell(), property);
+    if (dialog.open() == Window.OK) {
+    }
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java
new file mode 100644
index 0000000..5e05549
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/HtmlTooltipHelper.java
@@ -0,0 +1,332 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.table;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.LocationAdapter;
+import org.eclipse.swt.browser.LocationEvent;
+import org.eclipse.swt.browser.ProgressAdapter;
+import org.eclipse.swt.browser.ProgressEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.browser.IWebBrowser;
+import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
+import org.eclipse.wb.draw2d.IColorConstants;
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.EnvironmentUtils;
+import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;
+import org.eclipse.wb.internal.core.utils.ui.GridDataFactory;
+import org.eclipse.wb.internal.core.utils.ui.PixelConverter;
+
+import java.io.StringReader;
+import java.net.URL;
+import java.text.MessageFormat;
+
+/**
+ * Helper for displaying HTML tooltips.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property.table
+ */
+public final class HtmlTooltipHelper {
+  public static Control createTooltipControl(Composite parent, String header, String details) {
+    return createTooltipControl(parent, header, details, 0);
+  }
+
+  public static Control createTooltipControl(Composite parent,
+      String header,
+      String details,
+      int heightLimit) {
+    // prepare Control
+    Control control;
+    try {
+      String html = "<table cellspacing=2 cellpadding=0 border=0 margins=0 id=_wbp_tooltip_body>";
+      if (header != null) {
+        html += "<tr align=center><td><b>" + header + "</b></td></tr>";
+      }
+      html += "<tr><td align=justify>" + details + "</td></tr>";
+      html += "</table>";
+      control = createTooltipControl_Browser(parent, html, heightLimit);
+    } catch (Throwable e) {
+      control = createTooltipControl_Label(parent, details);
+    }
+    // set listeners
+    {
+      Listener listener = new Listener() {
+        @Override
+        public void handleEvent(Event event) {
+          Control tooltipControl = (Control) event.widget;
+          hideTooltip(tooltipControl);
+        }
+      };
+      control.addListener(SWT.MouseExit, listener);
+    }
+    // done
+    return control;
+  }
+
+  /**
+   * Creates {@link Browser} for displaying tooltip.
+   */
+  private static Control createTooltipControl_Browser(Composite parent,
+      String html,
+      final int heightLimitChars) {
+    // prepare styles
+    String styles;
+    try {
+        styles = DesignerPlugin.readFile(PropertyTable.class.getResourceAsStream("Tooltip.css"),
+                Charsets.US_ASCII);
+        if (styles == null) {
+            styles = "";
+        }
+    } catch (Throwable e) {
+      styles = "";
+    }
+    // prepare HTML with styles and tags
+    String wrappedHtml;
+    {
+      String bodyAttributes =
+          MessageFormat.format(
+              "text=''{0}'' bgcolor=''{1}''",
+              getColorWebString(IColorConstants.tooltipForeground),
+              getColorWebString(IColorConstants.tooltipBackground));
+      String closeElement =
+          EnvironmentUtils.IS_LINUX
+              ? "    <a href='' style='position:absolute;right:1em;' id=_wbp_close>Close</a>"
+              : "";
+      wrappedHtml =
+          /*CodeUtils.*/getSource(
+              "<html>",
+              "  <style CHARSET='ISO-8859-1' TYPE='text/css'>",
+              styles,
+              "  </style>",
+              "  <body " + bodyAttributes + ">",
+              closeElement,
+              html,
+              "  </body>",
+              "</html>");
+    }
+    // prepare Browser
+    final Browser browser = new Browser(parent, SWT.NONE);
+    browser.setText(wrappedHtml);
+    // open URLs in new window
+    browser.addLocationListener(new LocationAdapter() {
+      @Override
+      public void changing(LocationEvent event) {
+        event.doit = false;
+        hideTooltip((Browser) event.widget);
+        if (!"about:blank".equals(event.location)) {
+          try {
+            IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport();
+            IWebBrowser browserSupport = support.createBrowser("wbp.browser");
+            browserSupport.openURL(new URL(event.location));
+          } catch (Throwable e) {
+            DesignerPlugin.log(e);
+          }
+        }
+      }
+    });
+    // set size
+    {
+      int textLength = getTextLength(html);
+      // horizontal hint
+      int hintH = 50;
+      if (textLength < 100) {
+        hintH = 40;
+      }
+      // vertical hint
+      int hintV = textLength / hintH + 3;
+      hintV = Math.min(hintV, 8);
+      // do set
+      GridDataFactory.create(browser).hintC(hintH, hintV);
+    }
+    // tweak size after rendering HTML
+    browser.addProgressListener(new ProgressAdapter() {
+      @Override
+      public void completed(ProgressEvent event) {
+        browser.removeProgressListener(this);
+        tweakBrowserSize(browser, heightLimitChars);
+        browser.getShell().setVisible(true);
+      }
+    });
+    // done
+    return browser;
+  }
+
+  private static void tweakBrowserSize(Browser browser, int heightLimitChars) {
+    GridDataFactory.create(browser).grab().fill();
+    // limit height
+    if (heightLimitChars != 0) {
+      PixelConverter pixelConverter = new PixelConverter(browser);
+      int maxHeight = pixelConverter.convertHeightInCharsToPixels(heightLimitChars);
+      expandShellToShowFullPage_Height(browser, maxHeight);
+    }
+    // if no limit, then show all, so make as tall as required
+    if (heightLimitChars == 0) {
+      expandShellToShowFullPage_Height(browser, Integer.MAX_VALUE);
+    }
+  }
+
+  private static void expandShellToShowFullPage_Height(Browser browser, int maxHeight) {
+    try {
+      Shell shell = browser.getShell();
+      // calculate required
+      int contentHeight;
+      {
+        getContentOffsetHeight(browser);
+        contentHeight = getContentScrollHeight(browser);
+      }
+      // apply height
+      int useHeight = Math.min(contentHeight + ((EnvironmentUtils.IS_LINUX) ? 2 : 10), maxHeight);
+      shell.setSize(shell.getSize().x, useHeight);
+      // trim height to content
+      {
+        int offsetHeight = getBodyOffsetHeight(browser);
+        int scrollHeight = getBodyScrollHeight(browser);
+        int delta = scrollHeight - offsetHeight;
+        if (delta != 0 && delta < 10) {
+          Point size = shell.getSize();
+          shell.setSize(size.x, size.y + delta + 1);
+        }
+      }
+      // trim width to content
+      {
+        int offsetWidth = getContentOffsetWidth(browser);
+        {
+          Point size = shell.getSize();
+          shell.setSize(offsetWidth + ((EnvironmentUtils.IS_MAC) ? 6 : 10), size.y);
+        }
+      }
+      // hide 'Close' if too narrow
+      if (EnvironmentUtils.IS_LINUX) {
+        if (shell.getSize().y < 30) {
+          hideCloseElement(browser);
+        }
+      }
+    } catch (Throwable e) {
+    }
+  }
+
+  private static int getContentOffsetWidth(Browser browser) throws Exception {
+    return evaluateScriptInt(
+        browser,
+        "return document.getElementById('_wbp_tooltip_body').offsetWidth;");
+  }
+
+  private static int getContentOffsetHeight(Browser browser) throws Exception {
+    return evaluateScriptInt(
+        browser,
+        "return document.getElementById('_wbp_tooltip_body').offsetHeight;");
+  }
+
+  private static int getContentScrollHeight(Browser browser) throws Exception {
+    return evaluateScriptInt(
+        browser,
+        "return document.getElementById('_wbp_tooltip_body').scrollHeight;");
+  }
+
+  private static int getBodyOffsetHeight(Browser browser) throws Exception {
+    return evaluateScriptInt(browser, "return document.body.offsetHeight;");
+  }
+
+  private static int getBodyScrollHeight(Browser browser) throws Exception {
+    return evaluateScriptInt(browser, "return document.body.scrollHeight;");
+  }
+
+  private static int evaluateScriptInt(Browser browser, String script) throws Exception {
+    Object o = ReflectionUtils.invokeMethod(browser, "evaluate(java.lang.String)", script);
+    return ((Number) o).intValue();
+  }
+
+  private static void hideCloseElement(Browser browser) throws Exception {
+    String script = "document.getElementById('_wbp_close').style.display = 'none'";
+    ReflectionUtils.invokeMethod(browser, "evaluate(java.lang.String)", script);
+  }
+
+  /**
+   * @return the length of text in given HTML. Uses internal class, so may fail, in this case
+   *         returns length on HTML.
+   */
+  private static int getTextLength(String html) {
+    StringReader htmlStringReader = new StringReader(html);
+    try {
+      ClassLoader classLoader = PropertyTable.class.getClassLoader();
+      Class<?> readerClass =
+          classLoader.loadClass("org.eclipse.jface.internal.text.html.HTML2TextReader");
+      Object reader = readerClass.getConstructors()[0].newInstance(htmlStringReader, null);
+      String text = (String) ReflectionUtils.invokeMethod(reader, "getString()");
+      return text.length();
+    } catch (Throwable e) {
+      return html.length();
+    }
+  }
+
+  /**
+   * Returns a string representation of {@link Color} suitable for web pages.
+   *
+   * @param color
+   *          the {@link Color} instance, not <code>null</code>.
+   * @return a string representation of {@link Color} suitable for web pages.
+   */
+  private static String getColorWebString(final Color color) {
+    String colorString = "#" + Integer.toHexString(color.getRed());
+    colorString += Integer.toHexString(color.getGreen());
+    colorString += Integer.toHexString(color.getBlue());
+    return colorString;
+  }
+
+  /**
+   * Creates {@link Label} if {@link Browser} can not be used.
+   */
+  private static Control createTooltipControl_Label(Composite parent, String html) {
+    // prepare Label
+    final Label label = new Label(parent, SWT.WRAP);
+    label.setText(html);
+    // set size
+    int requiredWidth = label.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
+    GridDataFactory.create(label).hintHC(50).hintHMin(requiredWidth);
+    // copy colors
+    label.setForeground(parent.getForeground());
+    label.setBackground(parent.getBackground());
+    // done
+    parent.getDisplay().asyncExec(new Runnable() {
+      @Override
+    public void run() {
+        Shell shell = label.getShell();
+        shell.setVisible(true);
+      }
+    });
+    return label;
+  }
+
+  private static void hideTooltip(Control tooltip) {
+    tooltip.getShell().dispose();
+  }
+
+  // Copied from CodeUtils.java: CodeUtils.getSource()
+  /**
+   * @return the source as single {@link String}, lines joined using "\n".
+   */
+  public static String getSource(String... lines) {
+      return Joiner.on('\n').join(lines);
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyExceptionHandler.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyExceptionHandler.java
new file mode 100644
index 0000000..879f699
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyExceptionHandler.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.table;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
+
+/**
+ * Handler for any {@link Exception} that happens in {@link PropertyTable}, i.e. exceptions during
+ * {@link Property} modifications using {@link PropertyEditor}'s.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.table
+ */
+public interface IPropertyExceptionHandler {
+  void handle(Throwable e);
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyTooltipSite.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyTooltipSite.java
new file mode 100644
index 0000000..e2fee14
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/IPropertyTooltipSite.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.table;
+
+/**
+ * Interface that allows control of {@link PropertyTooltipProvider} interact with
+ * {@link PropertyTableTooltipHelper}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.table
+ */
+public interface IPropertyTooltipSite {
+  /**
+   * @return the {@link PropertyTable} of this site.
+   */
+  PropertyTable getTable();
+
+  /**
+   * Hides current tooltip.
+   */
+  void hideTooltip();
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java
new file mode 100644
index 0000000..7a49cb3
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java
@@ -0,0 +1,1602 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.table;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.wb.draw2d.IColorConstants;
+import org.eclipse.wb.draw2d.ICursorConstants;
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.category.PropertyCategory;
+import org.eclipse.wb.internal.core.model.property.category.PropertyCategoryProvider;
+import org.eclipse.wb.internal.core.model.property.category.PropertyCategoryProviders;
+import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.complex.IComplexPropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation;
+import org.eclipse.wb.internal.core.utils.check.Assert;
+import org.eclipse.wb.internal.core.utils.ui.DrawUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Control that can display {@link Property}'s and edit them using {@link PropertyEditor}'s.
+ *
+ * @author scheglov_ke
+ * @author lobas_av
+ * @coverage core.model.property.table
+ */
+public class PropertyTable extends Canvas implements ISelectionProvider {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Colors
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private static final Color COLOR_BACKGROUND = IColorConstants.listBackground;
+  private static final Color COLOR_NO_PROPERTIES = IColorConstants.gray;
+  private static final Color COLOR_LINE = IColorConstants.lightGray;
+  private static final Color COLOR_COMPLEX_LINE = DrawUtils.getShiftedColor(
+      IColorConstants.lightGray,
+      -32);
+  private static final Color COLOR_PROPERTY_BG = DrawUtils.getShiftedColor(COLOR_BACKGROUND, -12);
+  private static final Color COLOR_PROPERTY_BG_MODIFIED = COLOR_BACKGROUND;
+  private static final Color COLOR_PROPERTY_FG_TITLE = IColorConstants.listForeground;
+  private static final Color COLOR_PROPERTY_FG_VALUE =
+      DrawUtils.isDarkColor(IColorConstants.listBackground)
+          ? IColorConstants.lightBlue
+          : IColorConstants.darkBlue;
+  private static final Color COLOR_PROPERTY_BG_SELECTED = IColorConstants.listSelection;
+  private static final Color COLOR_PROPERTY_FG_SELECTED = IColorConstants.listSelectionText;
+  private static final Color COLOR_PROPERTY_FG_ADVANCED = IColorConstants.gray;
+  // BEGIN ADT MODIFICATIONS
+  public static final Color COLOR_PROPERTY_FG_DEFAULT = IColorConstants.darkGray;
+  // END ADT MODIFICATIONS
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Sizes
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private static final int MIN_COLUMN_WIDTH = 75;
+  private static final int MARGIN_LEFT = 2;
+  private static final int MARGIN_RIGHT = 1;
+  private static final int STATE_IMAGE_MARGIN_RIGHT = 4;
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Images
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private static final Image m_plusImage = DesignerPlugin.getImage("properties/plus.gif");
+  private static final Image m_minusImage = DesignerPlugin.getImage("properties/minus.gif");
+  private static int m_stateWidth = 9;
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance fields
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private final PropertyTableTooltipHelper m_tooltipHelper;
+  private boolean m_showAdvancedProperties;
+  private Property[] m_rawProperties;
+  private List<PropertyInfo> m_properties;
+  private final Set<String> m_expandedIds = Sets.newTreeSet();
+  // BEGIN ADT MODIFICATIONS
+  // Add support for "expand by default" : If you specify ids to be collapsed by
+  // default, then any *other* ids will be expanded by default.
+  private Set<String> m_collapsedIds = null;
+
+    /**
+     * Sets a set of ids that should be collapsed by default. Everything else
+     * will be expanded by default. If this method is not called, ids are
+     * collapsed by default instead.
+     *
+     * @param names set of ids to be collapsed
+     */
+  public void setDefaultCollapsedNames(Collection<String> names) {
+      m_collapsedIds = Sets.newTreeSet();
+      for (String name : names) {
+          m_collapsedIds.add("|" + name); // See PropertyInfo constructor for id syntax
+      }
+  }
+  // END ADT MODIFICATIONS
+
+  private Image m_bufferedImage;
+  private int m_rowHeight;
+  private int m_selection;
+  private int m_page;
+  private int m_splitter = -1;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public PropertyTable(Composite parent, int style) {
+    super(parent, style | SWT.V_SCROLL | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE);
+    hookControlEvents();
+    // calculate sizes
+    {
+      GC gc = new GC(this);
+      try {
+        m_rowHeight = 1 + gc.getFontMetrics().getHeight() + 1;
+      } finally {
+        gc.dispose();
+      }
+    }
+    // install tooltip helper
+    m_tooltipHelper = new PropertyTableTooltipHelper(this);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Events
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Adds listeners for events.
+   */
+  private void hookControlEvents() {
+    addListener(SWT.Dispose, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        disposeBufferedImage();
+      }
+    });
+    addListener(SWT.Resize, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        handleResize();
+      }
+    });
+    addListener(SWT.Paint, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        handlePaint(event.gc, event.x, event.y, event.width, event.height);
+      }
+    });
+    getVerticalBar().addListener(SWT.Selection, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        handleVerticalScrolling();
+      }
+    });
+    addMouseListener(new MouseAdapter() {
+      @Override
+      public void mouseDown(MouseEvent event) {
+        forceFocus();
+        handleMouseDown(event);
+      }
+
+      @Override
+      public void mouseUp(MouseEvent event) {
+        handleMouseUp(event);
+      }
+
+      @Override
+      public void mouseDoubleClick(MouseEvent event) {
+        handleMouseDoubleClick(event);
+      }
+    });
+    addMouseMoveListener(new MouseMoveListener() {
+      @Override
+    public void mouseMove(MouseEvent event) {
+        handleMouseMove(event);
+      }
+    });
+    // keyboard
+    addKeyListener(new KeyAdapter() {
+      @Override
+      public void keyPressed(KeyEvent e) {
+        handleKeyDown(e);
+      }
+    });
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Events: dispose, resize, scroll
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Disposes image used for double buffered painting.
+   */
+  private void disposeBufferedImage() {
+    if (m_bufferedImage != null) {
+      m_bufferedImage.dispose();
+      m_bufferedImage = null;
+    }
+  }
+
+  /**
+   * Handles {@link SWT#Resize} event.
+   */
+  private void handleResize() {
+    disposeBufferedImage();
+    configureScrolling();
+    // splitter
+    {
+      // set default value for splitter
+      if (m_splitter <= MIN_COLUMN_WIDTH) {
+        m_splitter = Math.max((int) (getClientArea().width * 0.4), MIN_COLUMN_WIDTH);
+      }
+      configureSplitter();
+    }
+  }
+
+  /**
+   * Handles {@link SWT#Selection} event for vertical {@link ScrollBar}.
+   */
+  private void handleVerticalScrolling() {
+    ScrollBar verticalBar = getVerticalBar();
+    if (verticalBar.getEnabled()) {
+      // update selection
+      m_selection = verticalBar.getSelection();
+      // redraw (but not include vertical bar to avoid flashing)
+      {
+        Rectangle clientArea = getClientArea();
+        redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);
+      }
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Keyboard
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Handles {@link SWT#KeyDown} event.
+   */
+  private void handleKeyDown(KeyEvent e) {
+    if (m_activePropertyInfo != null) {
+      try {
+        Property property = m_activePropertyInfo.getProperty();
+        // expand/collapse
+        if (m_activePropertyInfo.isComplex()) {
+          if (!m_activePropertyInfo.isExpanded()
+              && (e.character == '+' || e.keyCode == SWT.ARROW_RIGHT)) {
+            m_activePropertyInfo.expand();
+            configureScrolling();
+            return;
+          }
+          if (m_activePropertyInfo.isExpanded()
+              && (e.character == '-' || e.keyCode == SWT.ARROW_LEFT)) {
+            m_activePropertyInfo.collapse();
+            configureScrolling();
+            return;
+          }
+        }
+        // navigation
+        if (navigate(e)) {
+          return;
+        }
+        // editor activation
+        if (e.character == ' ' || e.character == SWT.CR) {
+          activateEditor(property, null);
+          return;
+        }
+        // DEL
+        if (e.keyCode == SWT.DEL) {
+          e.doit = false;
+          property.setValue(Property.UNKNOWN_VALUE);
+          return;
+        }
+        // send to editor
+        property.getEditor().keyDown(this, property, e);
+      } catch (Throwable ex) {
+        DesignerPlugin.log(ex);
+      }
+    }
+  }
+
+  /**
+   * @return <code>true</code> if given {@link KeyEvent} was navigation event, so new
+   *         {@link PropertyInfo} was selected.
+   */
+  public boolean navigate(KeyEvent e) {
+    int index = m_properties.indexOf(m_activePropertyInfo);
+    Rectangle clientArea = getClientArea();
+    //
+    int newIndex = index;
+    if (e.keyCode == SWT.HOME) {
+      newIndex = 0;
+    } else if (e.keyCode == SWT.END) {
+      newIndex = m_properties.size() - 1;
+    } else if (e.keyCode == SWT.PAGE_UP) {
+      newIndex = Math.max(index - m_page + 1, 0);
+    } else if (e.keyCode == SWT.PAGE_DOWN) {
+      newIndex = Math.min(index + m_page - 1, m_properties.size() - 1);
+    } else if (e.keyCode == SWT.ARROW_UP) {
+      newIndex = Math.max(index - 1, 0);
+    } else if (e.keyCode == SWT.ARROW_DOWN) {
+      newIndex = Math.min(index + 1, m_properties.size() - 1);
+    }
+    // activate new property
+    if (newIndex != index && newIndex < m_properties.size()) {
+      setActivePropertyInfo(m_properties.get(newIndex));
+      // check for scrolling
+      int y = m_rowHeight * (newIndex - m_selection);
+      if (y < 0) {
+        m_selection = newIndex;
+        configureScrolling();
+      } else if (y + m_rowHeight > clientArea.height) {
+        m_selection = newIndex - m_page + 1;
+        configureScrolling();
+      }
+      // repaint
+      redraw();
+      return true;
+    }
+    // no navigation change
+    return false;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Events: mouse
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private boolean m_splitterResizing;
+  /**
+   * We do expand/collapse on to events: click on state sign and on double click. But when we double
+   * click on state sign, we will have <em>two</em> events, so we should ignore double click.
+   */
+  private long m_lastExpandCollapseTime;
+
+  /**
+   * Handles {@link SWT#MouseDown} event.
+   */
+  private void handleMouseDown(MouseEvent event) {
+    m_splitterResizing = event.button == 1 && m_properties != null && isLocationSplitter(event.x);
+    // click in property
+    if (!m_splitterResizing && m_properties != null) {
+      int propertyIndex = getPropertyIndex(event.y);
+      if (propertyIndex >= m_properties.size()) {
+        return;
+      }
+      // prepare property
+      setActivePropertyInfo(m_properties.get(propertyIndex));
+      Property property = m_activePropertyInfo.getProperty();
+      // de-activate current editor
+      deactivateEditor(true);
+      redraw();
+      // activate editor
+      if (isLocationValue(event.x)) {
+        activateEditor(property, getValueRelativeLocation(event.x, event.y));
+      }
+    }
+  }
+
+  /**
+   * Handles {@link SWT#MouseUp} event.
+   */
+  private void handleMouseUp(MouseEvent event) {
+    if (event.button == 1) {
+      // resize splitter
+      if (m_splitterResizing) {
+        m_splitterResizing = false;
+        return;
+      }
+      // if out of bounds, then ignore
+      if (!getClientArea().contains(event.x, event.y)) {
+        return;
+      }
+      // update
+      if (m_properties != null) {
+        int index = getPropertyIndex(event.y);
+        if (index < m_properties.size()) {
+          PropertyInfo propertyInfo = m_properties.get(index);
+          // check for expand/collapse
+          if (isLocationState(propertyInfo, event.x)) {
+            try {
+              m_lastExpandCollapseTime = System.currentTimeMillis();
+              propertyInfo.flip();
+              configureScrolling();
+            } catch (Throwable e) {
+              DesignerPlugin.log(e);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Handles {@link SWT#MouseDoubleClick} event.
+   */
+  private void handleMouseDoubleClick(MouseEvent event) {
+    if (System.currentTimeMillis() - m_lastExpandCollapseTime > getDisplay().getDoubleClickTime()) {
+      try {
+        if (m_activePropertyInfo != null) {
+          if (m_activePropertyInfo.isComplex()) {
+            m_activePropertyInfo.flip();
+            configureScrolling();
+          } else {
+            Property property = m_activePropertyInfo.getProperty();
+            property.getEditor().doubleClick(property, getValueRelativeLocation(event.x, event.y));
+          }
+        }
+      } catch (Throwable e) {
+        handleException(e);
+      }
+    }
+  }
+
+  /**
+   * Handles {@link SWT#MouseMove} event.
+   */
+  private void handleMouseMove(MouseEvent event) {
+    int x = event.x;
+    // resize splitter
+    if (m_splitterResizing) {
+      m_splitter = x;
+      configureSplitter();
+      redraw();
+      return;
+    }
+    // if out of bounds, then ignore
+    if (!getClientArea().contains(event.x, event.y)) {
+      return;
+    }
+    // update
+    if (m_properties != null) {
+      // update cursor
+      if (isLocationSplitter(x)) {
+        setCursor(ICursorConstants.SIZEWE);
+      } else {
+        setCursor(null);
+      }
+      // update tooltip helper
+      updateTooltip(event);
+    } else {
+      updateTooltipNoProperty();
+    }
+  }
+
+  /**
+   * Updates {@link PropertyTableTooltipHelper}.
+   */
+  private void updateTooltip(MouseEvent event) {
+    int x = event.x;
+    int propertyIndex = getPropertyIndex(event.y);
+    //
+    if (propertyIndex < m_properties.size()) {
+      PropertyInfo propertyInfo = m_properties.get(propertyIndex);
+      Property property = propertyInfo.getProperty();
+      int y = (propertyIndex - m_selection) * m_rowHeight;
+      // check for title
+      {
+        int titleX = getTitleTextX(propertyInfo);
+        int titleRight = m_splitter - 2;
+        if (titleX <= x && x < titleRight) {
+          m_tooltipHelper.update(property, true, false, titleX, titleRight, y, m_rowHeight);
+          return;
+        }
+      }
+      // check for value
+      {
+        int valueX = m_splitter + 3;
+        if (x > valueX) {
+          m_tooltipHelper.update(
+              property,
+              false,
+              true,
+              valueX,
+              getClientArea().width,
+              y,
+              m_rowHeight);
+        }
+      }
+    } else {
+      updateTooltipNoProperty();
+    }
+  }
+
+  private void updateTooltipNoProperty() {
+    m_tooltipHelper.update(null, false, false, 0, 0, 0, 0);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Editor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private PropertyInfo m_activePropertyInfo;
+  private String m_activePropertyId;
+  private PropertyEditor m_activeEditor;
+
+  /**
+   * Tries to activate editor for {@link PropertyInfo} under cursor.
+   *
+   * @param location
+   *          the mouse location, if editor is activated using mouse click, or <code>null</code> if
+   *          it is activated using keyboard.
+   */
+  public void activateEditor(Property property, Point location) {
+    try {
+      // de-activate old editor
+      deactivateEditor(true);
+      // activate editor
+      PropertyEditor editor = property.getEditor();
+      try {
+        if (editor.activate(this, property, location)) {
+          m_activeEditor = editor;
+        }
+      } catch (Throwable e) {
+        handleException(e);
+      }
+      // set bounds
+      setActiveEditorBounds();
+    } catch (Throwable e) {
+      DesignerPlugin.log(e);
+    }
+  }
+
+  /**
+   * Deactivates current {@link PropertyEditor}.
+   */
+  public void deactivateEditor(boolean save) {
+    if (m_activeEditor != null) {
+      PropertyEditor activeEditor = m_activeEditor;
+      m_activeEditor = null;
+      if (m_activePropertyInfo != null && m_activePropertyInfo.m_property != null) {
+        activeEditor.deactivate(this, m_activePropertyInfo.m_property, save);
+      }
+    }
+  }
+
+  /**
+   * Sets correct bounds for active editor, for example we need update bounds after scrolling.
+   */
+  private void setActiveEditorBounds() {
+    if (m_activeEditor != null) {
+      int index = m_properties.indexOf(m_activePropertyInfo);
+      if (index == -1) {
+        // it is possible that active property was hidden because its parent was collapsed
+        deactivateEditor(true);
+      } else {
+        // prepare bounds for editor
+        Rectangle bounds;
+        {
+          Rectangle clientArea = getClientArea();
+          int x = m_splitter + 1;
+          int width = clientArea.width - x - MARGIN_RIGHT;
+          int y = m_rowHeight * (index - m_selection) + 1;
+          int height = m_rowHeight - 1;
+          bounds = new Rectangle(x, y, width, height);
+        }
+        // update bounds using presentation
+        {
+          PropertyEditorPresentation presentation = m_activeEditor.getPresentation();
+          if (presentation != null) {
+            int presentationWidth =
+                presentation.show(
+                    this,
+                    m_activePropertyInfo.m_property,
+                    bounds.x,
+                    bounds.y,
+                    bounds.width,
+                    bounds.height);
+            bounds.width -= presentationWidth;
+          }
+        }
+        // set editor bounds
+        m_activeEditor.setBounds(bounds);
+      }
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Exceptions
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private IPropertyExceptionHandler m_exceptionHandler;
+
+  /**
+   * Sets {@link IPropertyExceptionHandler} for handling all exceptions.
+   */
+  public void setExceptionHandler(IPropertyExceptionHandler exceptionHandler) {
+    m_exceptionHandler = exceptionHandler;
+  }
+
+  /**
+   * Handles given {@link Throwable}.<br>
+   * Right now it just logs it, but in future we can open some dialog here.
+   */
+  public void handleException(Throwable e) {
+    m_exceptionHandler.handle(e);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Scrolling
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Configures vertical {@link ScrollBar}.
+   */
+  private void configureScrolling() {
+    ScrollBar verticalBar = getVerticalBar();
+    if (m_properties == null) {
+      verticalBar.setEnabled(false);
+    } else {
+      m_page = getClientArea().height / m_rowHeight;
+      m_selection = Math.max(0, Math.min(m_properties.size() - m_page, m_selection));
+      verticalBar.setValues(m_selection, 0, m_properties.size(), m_page, 1, m_page);
+      // enable/disable scrolling
+      if (m_properties.size() <= m_page) {
+        verticalBar.setEnabled(false);
+      } else {
+        verticalBar.setEnabled(true);
+      }
+    }
+    // redraw, we reconfigure scrolling only if list of properties was changed, so we should redraw
+    redraw();
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Location/size utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the <code>X</code> position for first pixel of {@link PropertyInfo} title (location of
+   *         state image).
+   */
+  private int getTitleX(PropertyInfo propertyInfo) {
+    return MARGIN_LEFT + getLevelIndent() * propertyInfo.getLevel();
+  }
+
+  /**
+   * @return the <code>X</code> position for first pixel of {@link PropertyInfo} title text.
+   */
+  private int getTitleTextX(PropertyInfo propertyInfo) {
+    return getTitleX(propertyInfo) + getLevelIndent();
+  }
+
+  /**
+   * @return the indentation for single level.
+   */
+  private int getLevelIndent() {
+    return m_stateWidth + STATE_IMAGE_MARGIN_RIGHT;
+  }
+
+  /**
+   * Checks horizontal splitter value to boundary values.
+   */
+  private void configureSplitter() {
+    Rectangle clientArea = getClientArea();
+    // check title width
+    if (m_splitter < MIN_COLUMN_WIDTH) {
+      m_splitter = MIN_COLUMN_WIDTH;
+    }
+    // check value width
+    if (clientArea.width - m_splitter < MIN_COLUMN_WIDTH) {
+      m_splitter = clientArea.width - MIN_COLUMN_WIDTH;
+    }
+  }
+
+  /**
+   * @return the index in {@link #m_properties} corresponding given <code>y</code> location.
+   */
+  private int getPropertyIndex(int y) {
+    return m_selection + y / m_rowHeight;
+  }
+
+  /**
+   * @return <code>true</code> if given <code>x</code> coordinate is on state (plus/minus) image.
+   */
+  private boolean isLocationState(PropertyInfo propertyInfo, int x) {
+    int levelX = getTitleX(propertyInfo);
+    return propertyInfo.isComplex() && levelX <= x && x <= levelX + m_stateWidth;
+  }
+
+  /**
+   * Returns <code>true</code> if <code>x</code> coordinate is on splitter.
+   */
+  private boolean isLocationSplitter(int x) {
+    return Math.abs(m_splitter - x) < 2;
+  }
+
+  /**
+   * @return <code>true</code> if given <code>x</code> is on value part of property.
+   */
+  private boolean isLocationValue(int x) {
+    return x > m_splitter + 2;
+  }
+
+  /**
+   * @param x
+   *          the {@link PropertyTable} relative coordinate.
+   * @param y
+   *          the {@link PropertyTable} relative coordinate.
+   *
+   * @return the location relative to the value part of property.
+   */
+  private Point getValueRelativeLocation(int x, int y) {
+    return new Point(x - (m_splitter + 2), y - m_rowHeight * getPropertyIndex(y));
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Shows or hides {@link Property}-s with {@link PropertyCategory#ADVANCED}.
+   */
+  public void setShowAdvancedProperties(boolean showAdvancedProperties) {
+    m_showAdvancedProperties = showAdvancedProperties;
+    setInput0();
+  }
+
+  /**
+   * Sets the array of {@link Property}'s to display/edit.
+   */
+  public void setInput(Property[] properties) {
+    m_rawProperties = properties;
+    setInput0();
+  }
+
+  private void setInput0() {
+    // send "hide" to all PropertyEditorPresentation's
+    if (m_properties != null) {
+      for (PropertyInfo propertyInfo : m_properties) {
+        Property property = propertyInfo.getProperty();
+        // hide presentation
+        {
+          PropertyEditorPresentation presentation = property.getEditor().getPresentation();
+          if (presentation != null) {
+            presentation.hide(this, property);
+          }
+        }
+      }
+    }
+    // set new properties
+    if (m_rawProperties == null || m_rawProperties.length == 0) {
+      deactivateEditor(false);
+      m_properties = Lists.newArrayList();
+    } else {
+      try {
+        // add PropertyInfo for each Property
+        m_properties = Lists.newArrayList();
+        for (Property property : m_rawProperties) {
+          if (rawProperties_shouldShow(property)) {
+            PropertyInfo propertyInfo = new PropertyInfo(property);
+            m_properties.add(propertyInfo);
+          }
+        }
+        // expand properties using history
+        while (true) {
+          boolean expanded = false;
+          List<PropertyInfo> currentProperties = Lists.newArrayList(m_properties);
+          for (PropertyInfo propertyInfo : currentProperties) {
+            expanded |= propertyInfo.expandFromHistory();
+          }
+          // stop
+          if (!expanded) {
+            break;
+          }
+        }
+      } catch (Throwable e) {
+        DesignerPlugin.log(e);
+      }
+    }
+    // update active property
+    if (m_activePropertyId != null) {
+      PropertyInfo newActivePropertyInfo = null;
+      // try to find corresponding PropertyInfo
+      if (m_properties != null) {
+        for (PropertyInfo propertyInfo : m_properties) {
+          if (propertyInfo.m_id.equals(m_activePropertyId)) {
+            newActivePropertyInfo = propertyInfo;
+            break;
+          }
+        }
+      }
+      // set new PropertyInfo
+      setActivePropertyInfo(newActivePropertyInfo);
+    }
+    // update scroll bar
+    configureScrolling();
+  }
+
+  /**
+   * @return <code>true</code> if given {@link Property} should be displayed.
+   */
+  private boolean rawProperties_shouldShow(Property property) throws Exception {
+    PropertyCategory category = getCategory(property);
+    // filter out hidden properties
+    if (category.isHidden()) {
+      return false;
+    }
+    // filter out advanced properties
+    if (category.isAdvanced()) {
+      if (!m_showAdvancedProperties && !property.isModified()) {
+        return false;
+      }
+    }
+    if (category.isAdvancedReally()) {
+      return m_showAdvancedProperties;
+    }
+    // OK
+    return true;
+  }
+
+  /**
+   * Activates given {@link Property}.
+   */
+  public void setActiveProperty(Property property) {
+    for (PropertyInfo propertyInfo : m_properties) {
+      if (propertyInfo.m_property == property) {
+        setActivePropertyInfo(propertyInfo);
+        break;
+      }
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access: only for testing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the count of properties in "expanded" list.
+   */
+  public int forTests_getPropertiesCount() {
+    return m_properties.size();
+  }
+
+  /**
+   * @return the {@link Property} from "expanded" list.
+   */
+  public Property forTests_getProperty(int index) {
+    return m_properties.get(index).getProperty();
+  }
+
+  /**
+   * Expands the {@link PropertyInfo} with given index.
+   */
+  public void forTests_expand(int index) throws Exception {
+    m_properties.get(index).expand();
+  }
+
+  /**
+   * @return the position of splitter.
+   */
+  public int forTests_getSplitter() {
+    return m_splitter;
+  }
+
+  /**
+   * @return the location of state image (plus/minus) for given {@link Property}.
+   */
+  public Point forTests_getStateLocation(Property property) {
+    PropertyInfo propertyInfo = getPropertyInfo(property);
+    if (propertyInfo != null) {
+      int index = m_properties.indexOf(propertyInfo);
+      int x = getTitleX(propertyInfo);
+      int y = m_rowHeight * (index - m_selection) + 1;
+      return new Point(x, y);
+    }
+    return null;
+  }
+
+  /**
+   * @return the location of state image (plus/minus) for given {@link Property}.
+   */
+  public Point forTests_getValueLocation(Property property) {
+    PropertyInfo propertyInfo = getPropertyInfo(property);
+    if (propertyInfo != null) {
+      int index = m_properties.indexOf(propertyInfo);
+      int x = m_splitter + 5;
+      int y = m_rowHeight * (index - m_selection) + 1;
+      return new Point(x, y);
+    }
+    return null;
+  }
+
+  /**
+   * @return the active {@link PropertyEditor}.
+   */
+  public PropertyEditor forTests_getActiveEditor() {
+    return m_activeEditor;
+  }
+
+  /**
+   * @return the {@link PropertyCategory} that is used by this {@link PropertyTable} to display.
+   */
+  public PropertyCategory forTests_getCategory(Property property) {
+    return getCategory(property);
+  }
+
+  /**
+   * @return the {@link PropertyInfo}for given {@link Property}.
+   */
+  private PropertyInfo getPropertyInfo(Property property) {
+    for (PropertyInfo propertyInfo : m_properties) {
+      if (propertyInfo.getProperty() == property) {
+        return propertyInfo;
+      }
+    }
+    return null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // ISelectionProvider
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private final List<ISelectionChangedListener> m_selectionListeners = Lists.newArrayList();
+
+  @Override
+public void addSelectionChangedListener(ISelectionChangedListener listener) {
+    if (!m_selectionListeners.contains(listener)) {
+      m_selectionListeners.add(listener);
+    }
+  }
+
+  @Override
+public void removeSelectionChangedListener(ISelectionChangedListener listener) {
+    m_selectionListeners.add(listener);
+  }
+
+  @Override
+public ISelection getSelection() {
+    if (m_activePropertyInfo != null) {
+      return new StructuredSelection(m_activePropertyInfo.getProperty());
+    } else {
+      return StructuredSelection.EMPTY;
+    }
+  }
+
+  @Override
+  public void setSelection(ISelection selection) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets the new active {@link PropertyInfo} and sends event to {@link ISelectionChangedListener}
+   * 's.
+   */
+  private void setActivePropertyInfo(PropertyInfo activePropertyInfo) {
+    m_activePropertyInfo = activePropertyInfo;
+    // update m_activePropertyId only when really select property,
+    // not just remove selection because there are no corresponding property for old active
+    // so, later for some other component, if we don't select other property, old active will be selected
+    if (m_activePropertyInfo != null) {
+      m_activePropertyId = m_activePropertyInfo.m_id;
+    }
+    // make sure that active property is visible
+    if (m_activePropertyInfo != null) {
+      int row = m_properties.indexOf(m_activePropertyInfo);
+      if (m_selection <= row && row < m_selection + m_page) {
+      } else {
+        m_selection = row;
+        configureScrolling();
+      }
+    }
+    // send events
+    SelectionChangedEvent selectionEvent = new SelectionChangedEvent(this, getSelection());
+    for (ISelectionChangedListener listener : m_selectionListeners) {
+      listener.selectionChanged(selectionEvent);
+    }
+    // re-draw
+    redraw();
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Painting
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private boolean m_painting;
+  private Font m_baseFont;
+  private Font m_boldFont;
+  private Font m_italicFont;
+
+  /**
+   * Handles {@link SWT#Paint} event.
+   */
+  private void handlePaint(GC gc, int x, int y, int width, int height) {
+    // sometimes we disable Eclipse Shell to prevent user actions, but we do this for short time
+    if (!isEnabled()) {
+      return;
+    }
+    // prevent recursion
+    if (m_painting) {
+      return;
+    }
+    m_painting = true;
+    //
+    try {
+      setActiveEditorBounds();
+      // prepare buffered image
+      if (m_bufferedImage == null || m_bufferedImage.isDisposed()) {
+        Point size = getSize();
+        m_bufferedImage = new Image(DesignerPlugin.getStandardDisplay(), size.x, size.y);
+      }
+      // prepare buffered GC
+      GC bufferedGC = null;
+      try {
+        // perform some drawing
+        {
+          bufferedGC = new GC(m_bufferedImage);
+          bufferedGC.setClipping(x, y, width, height);
+          bufferedGC.setBackground(gc.getBackground());
+          bufferedGC.setForeground(gc.getForeground());
+          bufferedGC.setFont(gc.getFont());
+          bufferedGC.setLineStyle(gc.getLineStyle());
+          bufferedGC.setLineWidth(gc.getLineWidth());
+        }
+        // fill client area
+        {
+          Rectangle clientArea = getClientArea();
+          bufferedGC.setBackground(COLOR_BACKGROUND);
+          bufferedGC.fillRectangle(clientArea);
+        }
+        // draw content
+        if (m_properties == null || m_properties.size() == 0) {
+          drawEmptyContent(bufferedGC);
+        } else {
+          drawContent(bufferedGC);
+        }
+      } finally {
+        // flush image
+        if (bufferedGC != null) {
+          bufferedGC.dispose();
+        }
+      }
+      gc.drawImage(m_bufferedImage, 0, 0);
+    } finally {
+      m_painting = false;
+    }
+  }
+
+  /**
+   * Draws content when there are no properties.
+   */
+  private void drawEmptyContent(GC gc) {
+    Rectangle area = getClientArea();
+    // draw message
+    gc.setForeground(COLOR_NO_PROPERTIES);
+    DrawUtils.drawStringCHCV(
+        gc,
+        "<No properties>",
+        0,
+        0,
+        area.width,
+        area.height);
+  }
+
+  /**
+   * Draws all {@link PropertyInfo}'s, separators, etc.
+   */
+  private void drawContent(GC gc) {
+    Rectangle clientArea = getClientArea();
+    // prepare fonts
+    m_baseFont = gc.getFont();
+    m_boldFont = DrawUtils.getBoldFont(m_baseFont);
+    m_italicFont = DrawUtils.getItalicFont(m_baseFont);
+    // show presentations
+    int[] presentationsWidth = showPresentations(clientArea);
+    // draw properties
+    {
+      int y = clientArea.y - m_rowHeight * m_selection;
+      for (int i = 0; i < m_properties.size(); i++) {
+        // skip, if not visible yet
+        if (y + m_rowHeight < 0) {
+          y += m_rowHeight;
+          continue;
+        }
+        // stop, if already invisible
+        if (y > clientArea.height) {
+          break;
+        }
+        // draw single property
+        {
+          PropertyInfo propertyInfo = m_properties.get(i);
+          drawProperty(gc, propertyInfo, y + 1, m_rowHeight - 1, clientArea.width
+              - presentationsWidth[i]);
+          y += m_rowHeight;
+        }
+        // draw row separator
+        gc.setForeground(COLOR_LINE);
+        gc.drawLine(0, y, clientArea.width, y);
+      }
+    }
+    // draw expand line
+    drawExpandLines(gc, clientArea);
+    // draw rectangle around table
+    gc.setForeground(COLOR_LINE);
+    gc.drawRectangle(0, 0, clientArea.width - 1, clientArea.height - 1);
+    // draw splitter
+    gc.setForeground(COLOR_LINE);
+    gc.drawLine(m_splitter, 0, m_splitter, clientArea.height);
+    // dispose font
+    m_boldFont.dispose();
+    m_italicFont.dispose();
+  }
+
+  /**
+   * Shows {@link PropertyEditorPresentation}'s for all {@link Property}'s, i.e. updates also their
+   * bounds. So, some {@link PropertyEditorPresentation}'s become invisible because they are moved
+   * above or below visible client area.
+   *
+   * @return the array of width for each {@link PropertyEditorPresentation}'s, consumed on the
+   *         right.
+   */
+  private int[] showPresentations(Rectangle clientArea) {
+    int[] presentationsWidth = new int[m_properties.size()];
+    // prepare value rectangle
+    int x = m_splitter + 4;
+    int w = clientArea.width - x - MARGIN_RIGHT;
+    // show presentation's for all properties
+    int y = clientArea.y - m_rowHeight * m_selection;
+    for (int i = 0; i < m_properties.size(); i++) {
+      PropertyInfo propertyInfo = m_properties.get(i);
+      Property property = propertyInfo.getProperty();
+      PropertyEditorPresentation presentation = property.getEditor().getPresentation();
+      if (presentation != null) {
+        presentationsWidth[i] = presentation.show(this, property, x, y + 1, w, m_rowHeight - 1);
+      }
+      y += m_rowHeight;
+    }
+    return presentationsWidth;
+  }
+
+  /**
+   * Draws lines from expanded complex property to its last sub-property.
+   */
+  private void drawExpandLines(GC gc, Rectangle clientArea) {
+    int height = m_rowHeight - 1;
+    int xOffset = m_plusImage.getBounds().width / 2;
+    int yOffset = (height - m_plusImage.getBounds().width) / 2;
+    //
+    int y = clientArea.y - m_selection * m_rowHeight;
+    gc.setForeground(COLOR_COMPLEX_LINE);
+    for (int i = 0; i < m_properties.size(); i++) {
+      PropertyInfo propertyInfo = m_properties.get(i);
+      //
+      if (propertyInfo.isExpanded()) {
+        int index = m_properties.indexOf(propertyInfo);
+        // prepare index of last sub-property
+        int index2 = index;
+        for (; index2 < m_properties.size(); index2++) {
+          PropertyInfo nextPropertyInfo = m_properties.get(index2);
+          if (nextPropertyInfo != propertyInfo
+              && nextPropertyInfo.getLevel() <= propertyInfo.getLevel()) {
+            break;
+          }
+        }
+        index2--;
+        // draw line if there are children
+        if (index2 > index) {
+          int x = getTitleX(propertyInfo) + xOffset;
+          int y1 = y + height - yOffset;
+          int y2 = y + m_rowHeight * (index2 - index) + m_rowHeight / 2;
+          gc.drawLine(x, y1, x, y2);
+          gc.drawLine(x, y2, x + m_rowHeight / 3, y2);
+        }
+      }
+      //
+      y += m_rowHeight;
+    }
+  }
+
+  /**
+   * Draws single {@link PropertyInfo} in specified rectangle.
+   */
+  private void drawProperty(GC gc, PropertyInfo propertyInfo, int y, int height, int width) {
+    // remember colors
+    Color oldBackground = gc.getBackground();
+    Color oldForeground = gc.getForeground();
+    // draw property
+    try {
+      Property property = propertyInfo.getProperty();
+      boolean isActiveProperty =
+          m_activePropertyInfo != null && m_activePropertyInfo.getProperty() == property;
+      // set background
+    boolean modified = property.isModified();
+    {
+        if (isActiveProperty) {
+          gc.setBackground(COLOR_PROPERTY_BG_SELECTED);
+        } else {
+          if (modified) {
+            gc.setBackground(COLOR_PROPERTY_BG_MODIFIED);
+          } else {
+            gc.setBackground(COLOR_PROPERTY_BG);
+          }
+        }
+        gc.fillRectangle(0, y, width, height);
+      }
+      // draw state image
+      if (propertyInfo.isShowComplex()) {
+        Image stateImage = propertyInfo.isExpanded() ? m_minusImage : m_plusImage;
+        DrawUtils.drawImageCV(gc, stateImage, getTitleX(propertyInfo), y, height);
+      }
+      // draw title
+      {
+        // configure GC
+        {
+          gc.setForeground(COLOR_PROPERTY_FG_TITLE);
+          // check category
+          if (getCategory(property).isAdvanced()) {
+            gc.setForeground(COLOR_PROPERTY_FG_ADVANCED);
+            gc.setFont(m_italicFont);
+          } else if (getCategory(property).isPreferred() || getCategory(property).isSystem()) {
+            gc.setFont(m_boldFont);
+          }
+          // check for active
+          if (isActiveProperty) {
+            gc.setForeground(COLOR_PROPERTY_FG_SELECTED);
+          }
+        }
+        // paint title
+        int x = getTitleTextX(propertyInfo);
+        DrawUtils.drawStringCV(gc, property.getTitle(), x, y, m_splitter - x, height);
+      }
+      // draw value
+      {
+        // configure GC
+        gc.setFont(m_baseFont);
+        if (!isActiveProperty) {
+          gc.setForeground(COLOR_PROPERTY_FG_VALUE);
+        }
+        // prepare value rectangle
+        int x = m_splitter + 4;
+        int w = width - x - MARGIN_RIGHT;
+        // paint value
+
+        // BEGIN ADT MODIFICATIONS
+        if (!modified) {
+            gc.setForeground(COLOR_PROPERTY_FG_DEFAULT);
+        }
+        // END ADT MODIFICATIONS
+
+        property.getEditor().paint(property, gc, x, y, w, height);
+      }
+    } catch (Throwable e) {
+      DesignerPlugin.log(e);
+    } finally {
+      // restore colors
+      gc.setBackground(oldBackground);
+      gc.setForeground(oldForeground);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // PropertyCategory
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private PropertyCategoryProvider m_propertyCategoryProvider =
+      PropertyCategoryProviders.fromProperty();
+
+  /**
+   * Sets the {@link PropertyCategoryProvider} that can be used to tweak usual
+   * {@link PropertyCategory}.
+   */
+  public void setPropertyCategoryProvider(PropertyCategoryProvider propertyCategoryProvider) {
+    m_propertyCategoryProvider = propertyCategoryProvider;
+  }
+
+  /**
+   * @return the {@link PropertyCategory} that is used by this {@link PropertyTable} to display.
+   */
+  private PropertyCategory getCategory(Property property) {
+    return m_propertyCategoryProvider.getCategory(property);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // PropertyInfo
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Class with information about single {@link Property}.
+   *
+   * @author scheglov_ke
+   */
+  private final class PropertyInfo {
+    private final String m_id;
+    private final int m_level;
+    private final Property m_property;
+    private final boolean m_stateComplex;
+    private boolean m_stateExpanded;
+    private List<PropertyInfo> m_children;
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Constructor
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    public PropertyInfo(Property property) {
+      this(property, "", 0);
+    }
+
+    private PropertyInfo(Property property, String idPrefix, int level) {
+      // BEGIN ADT MODIFICATIONS
+      //m_id = idPrefix + "|" + property.getTitle();
+      m_id = idPrefix + "|" + property.getName();
+      // END ADT MODIFICATIONS
+      m_level = level;
+      m_property = property;
+      m_stateComplex = property.getEditor() instanceof IComplexPropertyEditor;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // State
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    /**
+     * @return <code>true</code> if this property is complex.
+     */
+    public boolean isComplex() {
+      return m_stateComplex;
+    }
+
+    public boolean isShowComplex() throws Exception {
+      if (m_stateComplex) {
+        prepareChildren();
+        return m_children != null && !m_children.isEmpty();
+      }
+      return false;
+    }
+
+    /**
+     * @return <code>true</code> if this complex property is expanded.
+     */
+    public boolean isExpanded() {
+      return m_stateExpanded;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Access
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    /**
+     * @return the level of this property, i.e. on which level of complex property it is located.
+     */
+    public int getLevel() {
+      return m_level;
+    }
+
+    /**
+     * @return the {@link Property}.
+     */
+    public Property getProperty() {
+      return m_property;
+    }
+
+    /**
+     * Flips collapsed/expanded state and adds/removes sub-properties.
+     */
+    public void flip() throws Exception {
+      Assert.isTrue(m_stateComplex);
+      if (m_stateExpanded) {
+        collapse();
+      } else {
+        expand();
+      }
+    }
+
+    /**
+     * Expands this property.
+     */
+    public void expand() throws Exception {
+      Assert.isTrue(m_stateComplex);
+      Assert.isTrue(!m_stateExpanded);
+      //
+      m_stateExpanded = true;
+      m_expandedIds.add(m_id);
+      // BEGIN ADT MODIFICATIONS
+      if (m_collapsedIds != null) {
+          m_collapsedIds.remove(m_id);
+      }
+      // END ADT MODIFICATIONS
+      prepareChildren();
+      //
+      int index = m_properties.indexOf(this);
+      addChildren(index + 1);
+    }
+
+    /**
+     * Collapses this property.
+     */
+    public void collapse() throws Exception {
+      Assert.isTrue(m_stateComplex);
+      Assert.isTrue(m_stateExpanded);
+      //
+      m_stateExpanded = false;
+      m_expandedIds.remove(m_id);
+      // BEGIN ADT MODIFICATIONS
+      if (m_collapsedIds != null) {
+          m_collapsedIds.add(m_id);
+      }
+      // END ADT MODIFICATIONS
+      prepareChildren();
+      //
+      int index = m_properties.indexOf(this);
+      removeChildren(index + 1);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Internal
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    /**
+     * Adds children properties.
+     *
+     * @return the index for new properties to add.
+     */
+    private int addChildren(int index) throws Exception {
+      prepareChildren();
+      for (PropertyInfo child : m_children) {
+        // skip if should not display raw Property
+        if (!rawProperties_shouldShow(child.m_property)) {
+          continue;
+        }
+        // add child
+        m_properties.add(index++, child);
+        // add children of current child
+        if (child.isExpanded()) {
+          index = child.addChildren(index);
+        }
+      }
+      return index;
+    }
+
+    /**
+     * Removes children properties.
+     */
+    private void removeChildren(int index) throws Exception {
+      prepareChildren();
+      for (PropertyInfo child : m_children) {
+        // skip if should not display raw Property
+        if (!rawProperties_shouldShow(child.m_property)) {
+          continue;
+        }
+        // hide presentation
+        {
+          PropertyEditorPresentation presentation =
+              child.getProperty().getEditor().getPresentation();
+          if (presentation != null) {
+            presentation.hide(PropertyTable.this, child.getProperty());
+          }
+        }
+        // remove child
+        m_properties.remove(index);
+        // remove children of current child
+        if (child.isExpanded()) {
+          child.removeChildren(index);
+        }
+      }
+    }
+
+    /**
+     * Prepares children {@link PropertyInfo}'s, for sub-properties.
+     */
+    private void prepareChildren() throws Exception {
+      if (m_children == null) {
+        m_children = Lists.newArrayList();
+        for (Property subProperty : getSubProperties()) {
+          PropertyInfo subPropertyInfo = createSubPropertyInfo(subProperty);
+          m_children.add(subPropertyInfo);
+        }
+      }
+    }
+
+    private PropertyInfo createSubPropertyInfo(Property subProperty) {
+      return new PropertyInfo(subProperty, m_id, m_level + 1);
+    }
+
+    private Property[] getSubProperties() throws Exception {
+      IComplexPropertyEditor complexEditor = (IComplexPropertyEditor) m_property.getEditor();
+      List<Property> subProperties = Lists.newArrayList();
+      for (Property subProperty : complexEditor.getProperties(m_property)) {
+        if (getCategory(subProperty).isHidden() && !subProperty.isModified()) {
+          // skip hidden properties
+          continue;
+        }
+        subProperties.add(subProperty);
+      }
+      return subProperties.toArray(new Property[subProperties.size()]);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Persistent expanding support
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    /**
+     * @return <code>true</code> if this {@link PropertyInfo} was expanded from history.
+     */
+    public boolean expandFromHistory() throws Exception {
+      if (isComplex() && !isExpanded() && m_expandedIds.contains(m_id)) {
+        expand();
+        return true;
+      }
+      // BEGIN ADT MODIFICATIONS
+      if (m_collapsedIds != null && isComplex() && !isExpanded()
+              && !m_collapsedIds.contains(m_id)) {
+          expand();
+          return true;
+      }
+      // END ADT MODIFICATIONS
+      return false;
+    }
+  }
+
+  // BEGIN ADT MODIFICATIONS
+  /** Collapse all top-level properties */
+  public void collapseAll() {
+      try {
+          m_lastExpandCollapseTime = System.currentTimeMillis();
+          if (m_collapsedIds != null) {
+              m_collapsedIds.addAll(m_expandedIds);
+          }
+          m_expandedIds.clear();
+          setInput(m_rawProperties);
+          redraw();
+      } catch (Throwable e) {
+          DesignerPlugin.log(e);
+      }
+  }
+
+  /** Expand all top-level properties */
+  public void expandAll() {
+      try {
+          m_lastExpandCollapseTime = System.currentTimeMillis();
+          if (m_collapsedIds != null) {
+              m_collapsedIds.clear();
+          }
+          m_expandedIds.clear();
+          for (PropertyInfo info : m_properties) {
+              if (info.m_stateComplex) {
+                  m_expandedIds.add(info.m_id);
+              }
+          }
+          setInput(m_rawProperties);
+          redraw();
+      } catch (Throwable e) {
+          DesignerPlugin.log(e);
+      }
+  }
+  // END ADT MODIFICATIONS
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTableTooltipHelper.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTableTooltipHelper.java
new file mode 100644
index 0000000..16b9d8f
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTableTooltipHelper.java
@@ -0,0 +1,191 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.table;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.wb.internal.core.EnvironmentUtils;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.utils.ui.GridLayoutFactory;
+
+/**
+ * Helper class for displaying tooltips.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property.table
+ */
+class PropertyTableTooltipHelper implements IPropertyTooltipSite {
+  private final PropertyTable m_table;
+  private Shell m_tooltip;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public PropertyTableTooltipHelper(PropertyTable table) {
+    m_table = table;
+    m_table.addListener(SWT.MouseHover, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        if (event.stateMask == 0) {
+          showTooltip();
+        }
+      }
+    });
+    m_table.addListener(SWT.MouseExit, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        // check, may be cursor is now on tooltip, so ignore this MouseExit
+        {
+          Control control = Display.getCurrent().getCursorControl();
+          while (control != null) {
+            if (control == m_tooltip) {
+              return;
+            }
+            control = control.getParent();
+          }
+        }
+        // no, we should hide tooltip
+        hideTooltip();
+      }
+    });
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private Property m_property;
+  private boolean m_onTitle;
+  private boolean m_onValue;
+  private int m_beginX;
+  private int m_endX;
+  private int m_y;
+  private int m_rowHeight;
+
+  /**
+   * {@link PropertyTable} call this method to inform that cursor location was changed.
+   */
+  public void update(Property property,
+      boolean onTitle,
+      boolean onValue,
+      int beginX,
+      int endX,
+      int y,
+      int rowHeight) {
+    m_property = property;
+    m_onTitle = onTitle;
+    m_onValue = onValue;
+    m_beginX = beginX;
+    m_endX = endX;
+    m_y = y;
+    m_rowHeight = rowHeight;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // IPropertyTooltipSite
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+public PropertyTable getTable() {
+    return m_table;
+  }
+
+  @Override
+public void hideTooltip() {
+    if (m_tooltip != null && !m_tooltip.isDisposed()) {
+      m_tooltip.dispose();
+    }
+    m_tooltip = null;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Showing tooltip
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private void showTooltip() {
+    hideTooltip();
+    // check for property
+    if (m_property == null) {
+      return;
+    }
+    //
+    if (m_onTitle) {
+      showTooltip(m_property.getAdapter(PropertyTooltipProvider.class), m_beginX, m_endX);
+    }
+    if (m_onValue) {
+      showTooltip(m_property.getEditor().getAdapter(PropertyTooltipProvider.class),
+              m_beginX, m_endX);
+    }
+  }
+
+  private void showTooltip(PropertyTooltipProvider provider, int startX, int endX) {
+      if (provider == null) {
+        return;
+      }
+    // create Shell
+    {
+      m_tooltip = new Shell(m_table.getShell(), SWT.NO_FOCUS | SWT.ON_TOP | SWT.TOOL | SWT.SINGLE);
+      configureColors(m_tooltip);
+      GridLayoutFactory.create(m_tooltip).noMargins();
+    }
+    // prepare control
+    Control control = provider.createTooltipControl(m_property, m_tooltip, endX - startX, this);
+    if (control == null) {
+      hideTooltip();
+      return;
+    }
+    // show Shell
+    {
+      // prepare tooltip location
+      Point tooltipLocation;
+      if (provider.getTooltipPosition() == PropertyTooltipProvider.ON) {
+        tooltipLocation = m_table.toDisplay(new Point(startX, m_y));
+      } else {
+        tooltipLocation = m_table.toDisplay(new Point(startX, m_y + m_rowHeight));
+      }
+      // set location/size and open
+      m_tooltip.setLocation(tooltipLocation.x, tooltipLocation.y);
+      // for non-windows systems the tooltip may have invalid tooltip bounds
+      // because some widget's API functions may fail if tooltip content is not visible
+      // ex., on MacOSX tree widget's items has zero bounds since they are not yet visible.
+      // the workaround is to preset tooltip size to big values before any computeSize called.
+      if (!EnvironmentUtils.IS_WINDOWS) {
+        m_tooltip.setSize(1000, 1000);
+      }
+      m_tooltip.setSize(m_tooltip.computeSize(SWT.DEFAULT, SWT.DEFAULT));
+      provider.show(m_tooltip);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets given {@link Control} correct background/foreground for tooltips.
+   */
+  private void configureColors(Control control) {
+    Display display = Display.getCurrent();
+    control.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
+    control.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipProvider.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipProvider.java
new file mode 100644
index 0000000..1c3d4fe
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipProvider.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.table;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Provider for tooltip controls.
+ * 
+ * @author scheglov_ke
+ * @coverage core.model.property.table
+ */
+public abstract class PropertyTooltipProvider {
+  /**
+   * Show tooltip directly on property row.
+   */
+  public static final int ON = 0;
+  /**
+   * Show tooltip below property row.
+   */
+  public static final int BELOW = 1;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // PropertyTooltipProvider
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Create tooltip control.
+   */
+  public abstract Control createTooltipControl(Property property,
+      Composite parent,
+      int availableWidth,
+      IPropertyTooltipSite site);
+
+  /**
+   * Shows tooltip {@link Shell}.
+   */
+  public void show(Shell shell) {
+    shell.setVisible(true);
+  }
+
+  /**
+   * Returns position for tooltip control. Usually we should show directly on same row, because we
+   * use tooltip to show just longer (full) text of property. But for "class" property we show
+   * hierarchy, so it is better show it below and allow user see also property row.
+   */
+  public int getTooltipPosition() {
+    return ON;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Tooltip listener
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * {@link Listener} that hides tooltip on mouse exit or click.
+   */
+  protected static final class HideListener implements Listener {
+    private final IPropertyTooltipSite m_site;
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Constructor
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    public HideListener(IPropertyTooltipSite site) {
+      m_site = site;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Listener
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    public void handleEvent(Event event) {
+      Control tooltipControl = (Control) event.widget;
+      switch (event.type) {
+        case SWT.MouseDown : {
+          PropertyTable table = m_site.getTable();
+          // convert location from tooltip to table
+          Point p = new Point(event.x, event.y);
+          p = tooltipControl.toDisplay(p);
+          p = table.toControl(p);
+          // send MouseDown to table
+          Event newEvent = new Event();
+          newEvent.x = p.x;
+          newEvent.y = p.y;
+          table.notifyListeners(SWT.MouseDown, newEvent);
+          // hide tooltip
+          m_site.hideTooltip();
+          break;
+        }
+        case SWT.MouseExit :
+          m_site.hideTooltip();
+          break;
+      }
+    }
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipTextProvider.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipTextProvider.java
new file mode 100644
index 0000000..b2e9b69
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTooltipTextProvider.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.table;
+
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.wb.internal.core.model.property.Property;
+
+/**
+ * Implementation of {@link PropertyTooltipProvider} for text.
+ *
+ * @author scheglov_ke
+ * @coverage core.model.property.table
+ */
+public abstract class PropertyTooltipTextProvider extends PropertyTooltipProvider {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // PropertyTooltipProvider
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public Control createTooltipControl(Property property,
+      Composite parent,
+      int availableWidth,
+      IPropertyTooltipSite site) {
+    // prepare header and content
+    String header = null;
+    String content = null;
+    try {
+        // BEGIN ADT MODIFICATIONS
+        // was: header = property.getTitle();
+        header = property.getName();
+        // END ADT MODIFICATIONS
+      content = getText(property);
+    } catch (Throwable e) {
+    }
+    if (header == null || content == null) {
+      return null;
+    }
+    // create tooltip Control
+    return HtmlTooltipHelper.createTooltipControl(parent, header, content, 8);
+  }
+
+  @Override
+  public void show(Shell shell) {
+    // do nothing, Shell will be displayed when Browser will complete rendering
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Text
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the text to show as tooltip.
+   */
+  protected abstract String getText(Property property) throws Exception;
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/Tooltip.css b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/Tooltip.css
new file mode 100644
index 0000000..2ba2a02
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/Tooltip.css
@@ -0,0 +1,35 @@
+/* Font definitions */
+html         { font-family: 'Segoe UI','Verdana','Helvetica',sans-serif; font-size: 9pt; font-style: normal; font-weight: normal; }
+body, h1, h2, h3, h4, h5, h6, p, table, td, caption, th, ul, ol, dl, li, dd, dt { font-size: 1em; }
+pre          { font-family: monospace; }
+
+/* Margins */
+body	     { overflow: auto; margin-top: 0px; margin-bottom: 0px; margin-left: 0.3em; margin-right: 0.3em; }
+div		     { margin: 0px; }
+h1           { margin-top: 0.3em; margin-bottom: 0.04em; }	
+h2           { margin-top: 2em; margin-bottom: 0.25em; }
+h3           { margin-top: 1.7em; margin-bottom: 0.25em; }
+h4           { margin-top: 2em; margin-bottom: 0.3em; }
+h5           { margin-top: 0px; margin-bottom: 0px; }
+p            { margin-top: 0em; margin-bottom: 0em; }
+pre          { margin-left: 0.6em; }
+ul	         { margin-top: 0px; margin-bottom: 1em; }
+li	         { margin-top: 0px; margin-bottom: 0px; } 
+li p	     { margin-top: 0px; margin-bottom: 0px; } 
+ol	         { margin-top: 0px; margin-bottom: 1em; }
+dl	         { margin-top: 0px; margin-bottom: 1em; }
+dt	         { margin-top: 0px; margin-bottom: 0px; font-weight: bold; }
+dd	         { margin-top: 0px; margin-bottom: 0px; }
+
+/* Styles and colors */
+a:link	     { color: #0000FF; }
+a:hover	     { color: #000080; }
+a:visited    { text-decoration: underline; }
+a.header:link    { text-decoration: none; color: InfoText }
+a.header:visited { text-decoration: none; color: InfoText }
+a.header:hover   { text-decoration: underline; color: #000080; }
+h4           { font-style: italic; }
+strong	     { font-weight: bold; }
+em	         { font-style: italic; }
+var	         { font-style: italic; }
+th	         { font-weight: bold; }
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/Pair.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/Pair.java
new file mode 100644
index 0000000..3df0e9b
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/Pair.java
@@ -0,0 +1,81 @@
+/*******************************************************************************

+ * Copyright (c) 2011 Google, Inc.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *    Google, Inc. - initial API and implementation

+ *******************************************************************************/

+package org.eclipse.wb.internal.core.utils;

+

+import com.google.common.base.Objects;

+

+/**

+ * Pair of two objects.

+ *

+ * @author scheglov_ke

+ * @coverage core.util

+ */

+public final class Pair<L, R> {

+  private final L left;

+  private final R right;

+

+  ////////////////////////////////////////////////////////////////////////////

+  //

+  // Constructor

+  //

+  ////////////////////////////////////////////////////////////////////////////

+  public Pair(L left, R right) {

+    this.left = left;

+    this.right = right;

+  }

+

+  ////////////////////////////////////////////////////////////////////////////

+  //

+  // Object

+  //

+  ////////////////////////////////////////////////////////////////////////////

+  @Override

+  public boolean equals(Object o) {

+    if (o == this) {

+      return true;

+    }

+    if (!(o instanceof Pair<?, ?>)) {

+      return false;

+    }

+    Pair<?, ?> other = (Pair<?, ?>) o;

+    return Objects.equal(getLeft(), other.getLeft())

+        && Objects.equal(getRight(), other.getRight());

+  }

+

+  @Override

+  public int hashCode() {

+    int hLeft = getLeft() == null ? 0 : getLeft().hashCode();

+    int hRight = getRight() == null ? 0 : getRight().hashCode();

+    return hLeft + 37 * hRight;

+  }

+

+  ////////////////////////////////////////////////////////////////////////////

+  //

+  // Access

+  //

+  ////////////////////////////////////////////////////////////////////////////

+  public L getLeft() {

+    return left;

+  }

+

+  public R getRight() {

+    return right;

+  }

+

+  ////////////////////////////////////////////////////////////////////////////

+  //

+  // Factory

+  //

+  ////////////////////////////////////////////////////////////////////////////

+  public static <L, R> Pair<L, R> create(L left, R right) {

+    return new Pair<L, R>(left, right);

+  }

+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/AbstractControlActionsManager.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/AbstractControlActionsManager.java
new file mode 100644
index 0000000..155b55c
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/AbstractControlActionsManager.java
@@ -0,0 +1,174 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.binding.editors.controls;
+
+import com.google.common.collect.Lists;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.commands.IHandler;
+import org.eclipse.core.expressions.EvaluationResult;
+import org.eclipse.core.expressions.Expression;
+import org.eclipse.core.expressions.ExpressionInfo;
+import org.eclipse.core.expressions.IEvaluationContext;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.IHandlerActivation;
+import org.eclipse.ui.handlers.IHandlerService;
+import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds;
+
+import java.util.List;
+
+/**
+ * Manager for installing/unistalling global handlers for {@link Control} actions commands.
+ *
+ * @author sablin_aa
+ * @author mitin_aa
+ */
+public abstract class AbstractControlActionsManager {
+  @SuppressWarnings("deprecation")
+  protected final Object[] COMMAND_HANDLER_IDS = new Object[]{
+      IWorkbenchActionDefinitionIds.COPY,
+      IWorkbenchActionDefinitionIds.CUT,
+      IWorkbenchActionDefinitionIds.PASTE,
+      IWorkbenchActionDefinitionIds.DELETE,
+      IWorkbenchActionDefinitionIds.SELECT_ALL,
+      IWorkbenchActionDefinitionIds.UNDO,
+      IWorkbenchActionDefinitionIds.REDO};
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected final Control m_control;
+  private boolean m_active = false;
+
+  public AbstractControlActionsManager(final Control control) {
+    m_control = control;
+    m_control.addFocusListener(new FocusListener() {
+      @Override
+    public void focusGained(FocusEvent e) {
+        activateHandlers();
+        m_active = true;
+      }
+
+      @Override
+    public void focusLost(FocusEvent e) {
+        deactivateHandlers();
+        m_active = false;
+      }
+    });
+    m_control.addDisposeListener(new DisposeListener() {
+      @Override
+    public void widgetDisposed(DisposeEvent e) {
+        if (m_active) {
+          // deactivate on dispose
+          deactivateHandlers();
+        }
+      }
+    });
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Handlers
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected static final IHandler EMPTY_HANDLER = new AbstractHandler() {
+    @Override
+    public Object execute(ExecutionEvent event) throws ExecutionException {
+      // do nothing
+      return null;
+    }
+
+    @Override
+    public boolean isEnabled() {
+      // of course, it is enabled ;)
+      return true;
+    }
+
+    @Override
+    public boolean isHandled() {
+      // we do not handle the actions; since action not handled, its' underlying SWT event
+      // would not be filtered by workbench, so it get a chance to be handled by SWT code
+      // or to be passed to the OS; see WorkbenchKeyboard.press() method.
+      return false;
+    }
+  };
+
+  /**
+   * @returns the global handler service.
+   */
+  public static IHandlerService getHandlerService() {
+    return (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class);
+  }
+
+  /**
+   * Activates the handlers for list of commands (see COMMAND_HANDLERS) with:<br>
+   * 1. The empty handler (except 'selectAll'), so underlying SWT event wouldn't be filtered by the
+   * workbench;<br>
+   * 2. Highest priority {@link Expression}, so this handler has a chance to be set.
+   */
+  protected void activateHandlers() {
+    IHandlerService service = getHandlerService();
+    for (int i = 0; i < COMMAND_HANDLER_IDS.length; ++i) {
+      String actionName = (String) COMMAND_HANDLER_IDS[i];
+      IHandler handler = getHandlerFor(actionName);
+      activateHandler(actionName, service, handler, new Expression() {
+        @Override
+        public EvaluationResult evaluate(IEvaluationContext context) throws CoreException {
+          return EvaluationResult.TRUE;
+        }
+
+        @Override
+        public void collectExpressionInfo(ExpressionInfo info) {
+          // get the highest priority
+          // note, if someone else has such priority, there will be key-binding conflicts logged.
+          info.markSystemPropertyAccessed();
+          info.markDefaultVariableAccessed();
+          info.addVariableNameAccess(ISources.ACTIVE_MENU_NAME);
+        }
+      });
+    }
+  }
+
+  protected IHandler getHandlerFor(String actionName) {
+    return EMPTY_HANDLER;
+  }
+
+  /**
+   * Activates handler and stores it into a collection for further deactivation.
+   */
+  private final List<IHandlerActivation> m_activations = Lists.newLinkedList();
+
+  private void activateHandler(String actionName,
+      IHandlerService service,
+      IHandler handler,
+      Expression highPriorityExpression) {
+    // activate handler and store it into a collection for further deactivation
+    m_activations.add(service.activateHandler(actionName, handler, highPriorityExpression));
+  }
+
+  /**
+   * Deactivates all handlers and clears handlers collection.
+   */
+  protected void deactivateHandlers() {
+    getHandlerService().deactivateHandlers(m_activations);
+    m_activations.clear();
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/DefaultControlActionsManager.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/DefaultControlActionsManager.java
new file mode 100644
index 0000000..ff048bc
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/binding/editors/controls/DefaultControlActionsManager.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.binding.editors.controls;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.commands.IHandler;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds;
+
+/**
+ * Default manager for installing/unistalling global handlers for {@link Control} actions commands.
+ * 
+ * @author sablin_aa
+ */
+public class DefaultControlActionsManager extends AbstractControlActionsManager {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public DefaultControlActionsManager(final Control control) {
+    super(control);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Handlers
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected IHandler getHandlerFor(String actionName) {
+    if (actionName.equalsIgnoreCase(IWorkbenchActionDefinitionIds.SELECT_ALL)) {
+      return SELECTALL_HANDLER;
+    }
+    return super.getHandlerFor(actionName);
+  }
+
+  /**
+   * Handler for process "Select all" action.
+   */
+  private final IHandler SELECTALL_HANDLER = new AbstractHandler() {
+    public Object execute(ExecutionEvent event) throws ExecutionException {
+      selectAllExecuted();
+      return null;
+    }
+
+    @Override
+    public boolean isEnabled() {
+      return true;
+    }
+
+    @Override
+    public boolean isHandled() {
+      return true;
+    }
+  };
+
+  protected void selectAllExecuted() {
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/check/Assert.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/check/Assert.java
new file mode 100644
index 0000000..28de0b6
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/check/Assert.java
@@ -0,0 +1,361 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.check;
+
+import java.text.MessageFormat;
+
+/**
+ * <code>Assert</code> is useful for for embedding runtime sanity checks in code. The predicate
+ * methods all test a condition and throw some type of unchecked exception if the condition does not
+ * hold.
+ * <p>
+ * Assertion failure exceptions, like most runtime exceptions, are thrown when something is
+ * misbehaving. Assertion failures are invariably unspecified behavior; consequently, clients should
+ * never rely on these being thrown (and certainly should not being catching them specifically).
+ *
+ * @author scheglov_ke
+ * @coverage core.util
+ */
+public final class Assert {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private Assert() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // "legal"
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Asserts that an argument is legal. If the given boolean is not <code>true</code>, an
+   * <code>IllegalArgumentException</code> is thrown.
+   *
+   * @param expression
+   *          the boolean expression of the check
+   * @return <code>true</code> if the check passes (does not return if the check fails)
+   * @exception IllegalArgumentException
+   *              if the legality test failed
+   */
+  public static boolean isLegal(boolean expression) {
+    return isLegal(expression, ""); //$NON-NLS-1$
+  }
+
+  /**
+   * Asserts that an argument is legal. If the given boolean is not <code>true</code>, an
+   * <code>IllegalArgumentException</code> is thrown. The given message is included in that
+   * exception, to aid debugging.
+   *
+   * @param expression
+   *          the boolean expression of the check
+   * @param message
+   *          the message to include in the exception
+   * @return <code>true</code> if the check passes (does not return if the check fails)
+   * @exception IllegalArgumentException
+   *              if the legality test failed
+   */
+  public static boolean isLegal(boolean expression, String message) {
+    if (!expression) {
+      throw new IllegalArgumentException(message);
+    }
+    return expression;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // "null"
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Asserts that the given object is <code>null</code>. If this is not the case, some kind of
+   * unchecked exception is thrown.
+   *
+   * @param object
+   *          the value to test
+   */
+  public static void isNull(Object object) {
+    isNull(object, ""); //$NON-NLS-1$
+  }
+
+  /**
+   * Asserts that the given object is <code>null</code>. If this is not the case, some kind of
+   * unchecked exception is thrown. The given message is included in that exception, to aid
+   * debugging.
+   *
+   * @param object
+   *          the value to test
+   * @param message
+   *          the message to include in the exception
+   */
+  public static void isNull(Object object, String message) {
+    if (object != null) {
+      throw new AssertionFailedException("null argument expected: " + message); //$NON-NLS-1$
+    }
+  }
+
+  /**
+   * Asserts that the given object is not <code>null</code>. If this is not the case, some kind of
+   * unchecked exception is thrown. The given message is included in that exception, to aid
+   * debugging.
+   *
+   * @param object
+   *          the value to test
+   * @param errorFormat
+   *          the format of error message to produce if the check fails, as expected by
+   *          {@link String#format(String, Object...)}. For example
+   *          <code>"Execution flow problem. %s expected, but %s found."</code>.
+   * @param args
+   *          the arguments for {@code errorFormat}
+   */
+  public static void isNull(Object object, String errorFormat, Object... args) {
+    if (object != null) {
+      fail("null argument expected: " + String.format(errorFormat, args)); //$NON-NLS-1$
+    }
+  }
+
+  /**
+   * @param errorFormat
+   *          the format of error message suitable for {@link MessageFormat}.
+   * @param errorFormat
+   *          the format of error message to produce if the check fails, as expected by
+   *          {@link MessageFormat}. For example
+   *          <code>"Execution flow problem. {0} expected, but {1} found."</code>.
+   */
+  public static void isNull2(Object object, String errorFormat, Object... args) {
+    if (object != null) {
+      String message = "null argument expected: " + MessageFormat.format(errorFormat, args); //$NON-NLS-1$
+      fail(message);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // not "null"
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Asserts that the given object is not <code>null</code>. If this is not the case, some kind of
+   * unchecked exception is thrown.
+   *
+   * @param object
+   *          the value to test
+   */
+  public static void isNotNull(Object object) {
+    isNotNull(object, ""); //$NON-NLS-1$
+  }
+
+  /**
+   * Asserts that the given object is not <code>null</code>. If this is not the case, some kind of
+   * unchecked exception is thrown. The given message is included in that exception, to aid
+   * debugging.
+   *
+   * @param object
+   *          the value to test
+   * @param message
+   *          the message to include in the exception
+   */
+  public static void isNotNull(Object object, String message) {
+    if (object == null) {
+      fail("null argument: " + message); //$NON-NLS-1$
+    }
+  }
+
+  /**
+   * Asserts that the given object is not <code>null</code>. If this is not the case, some kind of
+   * unchecked exception is thrown. The given message is included in that exception, to aid
+   * debugging.
+   *
+   * @param object
+   *          the value to test
+   * @param errorFormat
+   *          the format of error message to produce if the check fails, as expected by
+   *          {@link String#format(String, Object...)}. For example
+   *          <code>"Execution flow problem. %s expected, but %s found."</code>.
+   * @param args
+   *          the arguments for {@code errorFormat}
+   */
+  public static void isNotNull(Object object, String errorFormat, Object... args) {
+    if (object == null) {
+      fail("null argument: " + String.format(errorFormat, args)); //$NON-NLS-1$
+    }
+  }
+
+  /**
+   * @param errorFormat
+   *          the format of error message suitable for {@link MessageFormat}.
+   * @param errorFormat
+   *          the format of error message to produce if the check fails, as expected by
+   *          {@link MessageFormat}. For example
+   *          <code>"Execution flow problem. {0} expected, but {1} found."</code>.
+   */
+  public static void isNotNull2(Object object, String errorFormat, Object... args) {
+    if (object == null) {
+      String message = "null argument: " + MessageFormat.format(errorFormat, args); //$NON-NLS-1$
+      fail(message);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Fail
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Fails with given message.
+   *
+   * @param message
+   *          the message to include in the exception
+   */
+  public static void fail(String message) {
+    throw new AssertionFailedException(message);
+  }
+
+  /**
+   * @param errorFormat
+   *          the format of error message to produce if the check fails, as expected by
+   *          {@link MessageFormat}. For example <code>"{0} expected, but {1} found."</code>.
+   */
+  public static void fail(String errorFormat, Object... args) {
+    String message = MessageFormat.format(errorFormat, args);
+    throw new AssertionFailedException(message);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // "true"
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Asserts that the given boolean is <code>true</code>. If this is not the case, some kind of
+   * unchecked exception is thrown.
+   *
+   * @param expression
+   *          the boolean expression of the check
+   * @return <code>true</code> if the check passes (does not return if the check fails)
+   */
+  public static boolean isTrue(boolean expression) {
+    return isTrue(expression, ""); //$NON-NLS-1$
+  }
+
+  /**
+   * Asserts that the given boolean is <code>true</code>. If this is not the case, some kind of
+   * unchecked exception is thrown. The given message is included in that exception, to aid
+   * debugging.
+   *
+   * @param expression
+   *          the boolean expression of the check
+   * @param message
+   *          the message to include in the exception
+   * @return <code>true</code> if the check passes (does not return if the check fails)
+   */
+  public static boolean isTrue(boolean expression, String message) {
+    if (!expression) {
+      fail("assertion failed: " + message); //$NON-NLS-1$
+    }
+    return expression;
+  }
+
+  /**
+   * Asserts that the given boolean is <code>true</code>. If this is not the case, some kind of
+   * unchecked exception is thrown. The given message is included in that exception, to aid
+   * debugging.
+   *
+   * @param expression
+   *          the boolean expression of the check
+   * @param errorFormat
+   *          the format of error message to produce if the check fails, as expected by
+   *          {@link String#format(String, Object...)}. For example
+   *          <code>"Execution flow problem. %s expected, but %s found."</code>.
+   * @param args
+   *          the arguments for {@code errorFormat}
+   * @return <code>true</code> if the check passes (does not return if the check fails)
+   */
+  public static boolean isTrue(boolean expression, String errorFormat, Object... args) {
+    if (!expression) {
+      fail("assertion failed: " + String.format(errorFormat, args)); //$NON-NLS-1$
+    }
+    return expression;
+  }
+
+  /**
+   * Asserts that the given boolean is <code>true</code>. If this is not the case, some kind of
+   * unchecked exception is thrown. The given message is included in that exception, to aid
+   * debugging.
+   *
+   * @param expression
+   *          the boolean expression to check.
+   * @param errorFormat
+   *          the format of error message to produce if the check fails, as expected by
+   *          {@link MessageFormat}. For example <code>"{0} expected, but {1} found."</code>.
+   */
+  public static boolean isTrue2(boolean expression, String errorFormat, Object... args) {
+    if (!expression) {
+      fail(errorFormat, args);
+    }
+    return expression;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // equals
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Asserts that given actual value equals expected value. If this is not the case, some kind of
+   * unchecked exception is thrown.
+   *
+   * @param expected
+   *          the expected value
+   * @param the
+   *          actual value to check
+   */
+  public static void equals(int expected, int actual) {
+    equals(expected, actual, expected + " expected, but " + actual + " found");
+  }
+
+  /**
+   * Asserts that given actual value equals expected value. If this is not the case, some kind of
+   * unchecked exception is thrown. The given message is included in that exception, to aid
+   * debugging.
+   *
+   * @param expected
+   *          the expected value
+   * @param the
+   *          actual value to check
+   * @param message
+   *          the message to include in the exception
+   */
+  public static void equals(int expected, int actual, String message) {
+    if (expected != actual) {
+      fail("assertation failed: " + message);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // instanceOf
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Asserts that given object is not <code>null</code> and has class compatible with given.
+   */
+  public static void instanceOf(Class<?> expectedClass, Object o) {
+    if (o == null) {
+      fail(expectedClass.getName() + " expected, but 'null' found.");
+    }
+    if (!expectedClass.isAssignableFrom(o.getClass())) {
+      fail(expectedClass.getName() + " expected, but " + o.getClass().getName() + " found.");
+    }
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/check/AssertionFailedException.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/check/AssertionFailedException.java
new file mode 100644
index 0000000..d0c9794
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/check/AssertionFailedException.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.check;
+
+/**
+ * <code>AssertionFailedException</code> is a runtime exception thrown by some of the methods in
+ * <code>Assert</code>.
+ * 
+ * @author scheglov_ke
+ * @coverage core.util
+ */
+public final class AssertionFailedException extends RuntimeException {
+  private static final long serialVersionUID = 0L;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Constructs a new exception with the given message.
+   * 
+   * @param detail
+   *          the message
+   */
+  public AssertionFailedException(String detail) {
+    super(detail);
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/ExecutionUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/ExecutionUtils.java
new file mode 100644
index 0000000..d80ff94
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/ExecutionUtils.java
@@ -0,0 +1,292 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.execution;
+
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;
+
+import java.beans.Beans;
+
+/**
+ * Utilities for executing actions, such as {@link RunnableEx}.
+ *
+ * @author scheglov_ke
+ * @coverage core.util
+ */
+public class ExecutionUtils {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private ExecutionUtils() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Sleep
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sleeps given number of milliseconds, ignoring exceptions.
+   */
+  public static void sleep(final int millis) {
+    runIgnore(new RunnableEx() {
+      @Override
+    public void run() throws Exception {
+        Thread.sleep(millis);
+      }
+    });
+  }
+
+  /**
+   * Waits given number of milliseconds and runs events loop every 1 millisecond. At least one
+   * events loop will be executed. If current thread is not UI thread, then this method works just
+   * as {@link #sleep(int)}.
+   */
+  public static void waitEventLoop(int millis) {
+    Display display = Display.getCurrent();
+    if (display != null) {
+      long nanos = millis * 1000000L;
+      long start = System.nanoTime();
+      do {
+        sleep(0);
+        while (display.readAndDispatch()) {
+          // do nothing
+        }
+      } while (System.nanoTime() - start < nanos);
+    } else {
+      sleep(millis);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // void
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Runs given {@link RunnableEx} and ignores exceptions.
+   *
+   * @return <code>true</code> if execution was finished without exception.
+   */
+  public static boolean runIgnore(RunnableEx runnable) {
+    try {
+      runnable.run();
+      return true;
+    } catch (Throwable e) {
+      return false;
+    }
+  }
+
+  /**
+   * Runs given {@link RunnableEx} and logs exceptions using {@link DesignerPlugin#log(Throwable)}.
+   *
+   * @return <code>true</code> if execution was finished without exception.
+   */
+  public static boolean runLog(RunnableEx runnable) {
+    try {
+      runnable.run();
+      return true;
+    } catch (Throwable e) {
+      DesignerPlugin.log(e);
+      return false;
+    }
+  }
+
+  /**
+   * Runs given {@link RunnableEx} and re-throws exceptions using {@link RuntimeException}.
+   */
+  public static void runRethrow(RunnableEx runnable) {
+    try {
+      runnable.run();
+    } catch (Throwable e) {
+      throw ReflectionUtils.propagate(e);
+    }
+  }
+
+  /**
+   * Runs given {@link RunnableEx} and re-throws exceptions using {@link RuntimeException}.
+   */
+  public static void runRethrow(RunnableEx runnable, String format, Object... args) {
+    try {
+      runnable.run();
+    } catch (Throwable e) {
+      String message = String.format(format, args);
+      throw new RuntimeException(message, e);
+    }
+  }
+
+  /**
+   * Ensures that {@link Beans#isDesignTime()} returns <code>true</code> and runs given
+   * {@link RunnableEx}.
+   */
+  public static void runDesignTime(RunnableEx runnable) throws Exception {
+    boolean old_designTime = Beans.isDesignTime();
+    try {
+      Beans.setDesignTime(true);
+      runnable.run();
+    } finally {
+      Beans.setDesignTime(old_designTime);
+    }
+  }
+
+  /**
+   * Ensures that {@link Beans#isDesignTime()} returns <code>true</code> and runs given
+   * {@link RunnableEx}.
+   */
+  public static <T> T runDesignTime(RunnableObjectEx<T> runnable) throws Exception {
+    boolean old_designTime = Beans.isDesignTime();
+    try {
+      Beans.setDesignTime(true);
+      return runnable.runObject();
+    } finally {
+      Beans.setDesignTime(old_designTime);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // UI
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Runs given {@link RunnableEx} inside of UI thread, using {@link Display#syncExec(Runnable)}.
+   *
+   * @return <code>true</code> if {@link RunnableEx} was executed without any {@link Exception}.
+   */
+  public static boolean runLogUI(final RunnableEx runnable) {
+    final boolean[] success = new boolean[1];
+    Display.getDefault().syncExec(new Runnable() {
+      @Override
+    public void run() {
+        success[0] = ExecutionUtils.runLog(runnable);
+      }
+    });
+    return success[0];
+  }
+
+  /**
+   * Runs given {@link RunnableEx} inside of UI thread, using {@link Display#syncExec(Runnable)}.
+   */
+  public static void runRethrowUI(final RunnableEx runnable) {
+    Display.getDefault().syncExec(new Runnable() {
+      @Override
+    public void run() {
+        ExecutionUtils.runRethrow(runnable);
+      }
+    });
+  }
+
+  /**
+   * Runs given {@link RunnableEx} within UI thread using {@link Display#asyncExec(Runnable)}. Logs
+   * a {@link Throwable} which may occur.
+   */
+  public static void runAsync(final RunnableEx runnable) {
+    Display.getDefault().asyncExec(new Runnable() {
+      @Override
+    public void run() {
+        ExecutionUtils.runLog(runnable);
+      }
+    });
+  }
+
+  /**
+   * Runs given {@link RunnableEx} inside of UI thread, using {@link Display#syncExec(Runnable)}.
+   */
+  @SuppressWarnings("unchecked")
+  public static <T> T runObjectUI(final RunnableObjectEx<T> runnable) {
+    final Object[] result = new Object[1];
+    runRethrowUI(new RunnableEx() {
+      @Override
+    public void run() throws Exception {
+        result[0] = runObject(runnable);
+      }
+    });
+    return (T) result[0];
+  }
+
+  /**
+   * Runs given {@link RunnableEx} as {@link #runLog(RunnableEx)}, but using
+   * {@link Display#asyncExec(Runnable)}.
+   */
+  public static void runLogLater(final RunnableEx runnable) {
+    Display.getDefault().asyncExec(new Runnable() {
+      @Override
+    public void run() {
+        ExecutionUtils.runLog(runnable);
+      }
+    });
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Object
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Runs given {@link RunnableEx} and re-throws exceptions using {@link RuntimeException}.
+   *
+   * @return the {@link Object} returned by {@link RunnableEx#run()}.
+   */
+  public static <T> T runObject(RunnableObjectEx<T> runnable) {
+    try {
+      return runnable.runObject();
+    } catch (Throwable e) {
+      throw ReflectionUtils.propagate(e);
+    }
+  }
+
+  /**
+   * Runs given {@link RunnableEx} and re-throws exceptions using {@link RuntimeException}.
+   *
+   * @return the {@link Object} returned by {@link RunnableEx#run()}.
+   */
+  public static <T> T runObject(RunnableObjectEx<T> runnable, String format, Object... args) {
+    try {
+      return runnable.runObject();
+    } catch (Throwable e) {
+      String message = String.format(format, args);
+      throw new Error(message, e);
+    }
+  }
+
+  /**
+   * Runs given {@link RunnableEx} and ignores exceptions.
+   *
+   * @return the {@link Object} returned by {@link RunnableEx#run()} or <code>defaultValue</code> if
+   *         exception happened.
+   */
+  public static <T> T runObjectIgnore(RunnableObjectEx<T> runnable, T defaultValue) {
+    try {
+      return runnable.runObject();
+    } catch (Throwable e) {
+      return defaultValue;
+    }
+  }
+
+  /**
+   * Runs given {@link RunnableEx} and logs exceptions using {@link DesignerPlugin#log(Throwable)}.
+   *
+   * @return the {@link Object} returned by {@link RunnableEx#run()} or <code>defaultValue</code> if
+   *         exception was logged.
+   */
+  public static <T> T runObjectLog(RunnableObjectEx<T> runnable, T defaultValue) {
+    try {
+      return runnable.runObject();
+    } catch (Throwable e) {
+      DesignerPlugin.log(e);
+      return defaultValue;
+    }
+  }
+
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableEx.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableEx.java
new file mode 100644
index 0000000..18c141f
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableEx.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.execution;
+
+/**
+ * Analog of {@link Runnable} where method <code>run</code> can throw {@link Exception}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.util
+ */
+public interface RunnableEx {
+  /**
+   * Executes operation that can cause {@link Exception}.
+   */
+  void run() throws Exception;
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableObjectEx.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableObjectEx.java
new file mode 100644
index 0000000..02a2865
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/execution/RunnableObjectEx.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.execution;
+
+/**
+ * Analog of {@link Runnable} where method <code>run</code> can throw {@link Exception}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.util
+ */
+public interface RunnableObjectEx<T> {
+  /**
+   * Executes operation that can cause {@link Exception}.
+   * 
+   * @return some {@link Object} result for caller.
+   */
+  T runObject() throws Exception;
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassLoaderLocalMap.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassLoaderLocalMap.java
new file mode 100644
index 0000000..b74ba88
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassLoaderLocalMap.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.reflect;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Helper for setting properties for {@link ClassLoader}.
+ * <p>
+ * http://java.dzone.com/articles/classloaderlocal-how-avoid
+ * 
+ * @author Jevgeni Kabanov
+ * @author scheglov_ke
+ * @coverage core.util
+ */
+@SuppressWarnings("unchecked")
+public class ClassLoaderLocalMap implements Opcodes {
+  private static final String NAME = "GEN$$ClassLoaderProperties";
+  private static final Map<Object, Object> globalMap =
+      Collections.synchronizedMap(new WeakHashMap<Object, Object>());
+  private static Method defineMethod;
+  private static Method findLoadedClass;
+  static {
+    try {
+      defineMethod =
+          ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{
+              String.class,
+              byte[].class,
+              int.class,
+              int.class});
+      defineMethod.setAccessible(true);
+      findLoadedClass =
+          ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[]{String.class});
+      findLoadedClass.setAccessible(true);
+    } catch (NoSuchMethodException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Map
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public static boolean containsKey(ClassLoader cl, Object key) {
+    if (cl == null) {
+      return globalMap.containsKey(key);
+    }
+    // synchronizing over ClassLoader is usually safest
+    synchronized (cl) {
+      if (!hasHolder(cl)) {
+        return false;
+      }
+      return getLocalMap(cl).containsKey(key);
+    }
+  }
+
+  public static void put(ClassLoader cl, Object key, Object value) {
+    if (cl == null) {
+      globalMap.put(key, value);
+      return;
+    }
+    // synchronizing over ClassLoader is usually safest
+    synchronized (cl) {
+      getLocalMap(cl).put(key, value);
+    }
+  }
+
+  public static Object get(ClassLoader cl, Object key) {
+    if (cl == null) {
+      return globalMap.get(key);
+    }
+    // synchronizing over ClassLoader is usually safest
+    synchronized (cl) {
+      return getLocalMap(cl).get(key);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Implementation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private static boolean hasHolder(ClassLoader cl) {
+    String propertiesClassName = NAME;
+    try {
+      Class<?> clazz = (Class<?>) findLoadedClass.invoke(cl, new Object[]{propertiesClassName});
+      if (clazz == null) {
+        return false;
+      }
+    } catch (IllegalArgumentException e) {
+      throw new RuntimeException(e);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException(e);
+    } catch (InvocationTargetException e) {
+      throw new RuntimeException(e.getTargetException());
+    }
+    return true;
+  }
+
+  private static Map<Object, Object> getLocalMap(ClassLoader cl) {
+    String holderClassName = NAME;
+    Class<?> holderClass;
+    try {
+      holderClass = (Class<?>) findLoadedClass.invoke(cl, new Object[]{holderClassName});
+    } catch (IllegalArgumentException e) {
+      throw new RuntimeException(e);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException(e);
+    } catch (InvocationTargetException e) {
+      throw new RuntimeException(e.getTargetException());
+    }
+    if (holderClass == null) {
+      byte[] classBytes = buildHolderByteCode(holderClassName);
+      try {
+        holderClass =
+            (Class<?>) defineMethod.invoke(
+                cl,
+                new Object[]{
+                    holderClassName,
+                    classBytes,
+                    Integer.valueOf(0),
+                    Integer.valueOf(classBytes.length)});
+      } catch (InvocationTargetException e1) {
+        throw new RuntimeException(e1.getTargetException());
+      } catch (Throwable e1) {
+        throw new RuntimeException(e1);
+      }
+    }
+    try {
+      return (Map<Object, Object>) holderClass.getDeclaredField("localMap").get(null);
+    } catch (Throwable e1) {
+      throw new RuntimeException(e1);
+    }
+  }
+
+  private static byte[] buildHolderByteCode(String holderClassName) {
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    cw.visit(V1_2, ACC_PUBLIC + ACC_SUPER, holderClassName, null, "java/lang/Object", null);
+    {
+      fv =
+          cw.visitField(
+              ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
+              "localMap",
+              "Ljava/util/Map;",
+              null,
+              null);
+      fv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, "java/util/WeakHashMap");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/util/WeakHashMap", "<init>", "()V");
+      mv.visitFieldInsn(PUTSTATIC, holderClassName, "localMap", "Ljava/util/Map;");
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(2, 0);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+    return cw.toByteArray();
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassMap.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassMap.java
new file mode 100644
index 0000000..ce0d339
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ClassMap.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.reflect;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@link Map}-like interface for mapping {@link Class} to value.
+ * 
+ * @author scheglov_ke
+ * @coverage core.util
+ */
+public final class ClassMap<V> {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Creates new instance of {@link ClassMap}.
+   */
+  public static <V> ClassMap<V> create() {
+    return new ClassMap<V>();
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Map
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public void put(Class<?> key, V value) {
+    getMap(key).put(key, value);
+  }
+
+  public V get(Class<?> key) {
+    return getMap(key).get(key);
+  }
+
+  public void remove(Class<?> key) {
+    getMap(key).remove(key);
+  }
+
+  public void clear(ClassLoader classLoader) {
+    getMap(classLoader).clear();
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Implementation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private Map<Class<?>, V> getMap(Class<?> key) {
+    ClassLoader classLoader = key.getClassLoader();
+    return getMap(classLoader);
+  }
+
+  @SuppressWarnings("unchecked")
+  private Map<Class<?>, V> getMap(ClassLoader classLoader) {
+    Object map = ClassLoaderLocalMap.get(classLoader, this);
+    if (map == null) {
+      map = new HashMap<Class<?>, V>();
+      ClassLoaderLocalMap.put(classLoader, this, map);
+    }
+    return (Map<Class<?>, V>) map;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ReflectionUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ReflectionUtils.java
new file mode 100644
index 0000000..b362211
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/reflect/ReflectionUtils.java
@@ -0,0 +1,327 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.reflect;
+
+import com.google.common.collect.Maps;
+
+import org.eclipse.wb.internal.core.utils.check.Assert;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.Map;
+
+/**
+ * Contains different Java reflection utilities.
+ *
+ * @author scheglov_ke
+ * @coverage core.util
+ */
+public class ReflectionUtils {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private ReflectionUtils() {
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Signature
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @param runtime
+   *          is <code>true</code> if we need name for class loading, <code>false</code> if we need
+   *          name for source generation.
+   *
+   * @return the fully qualified name of given {@link Type}.
+   */
+  public static String getFullyQualifiedName(Type type, boolean runtime) {
+    Assert.isNotNull(type);
+    // Class
+    if (type instanceof Class<?>) {
+      Class<?> clazz = (Class<?>) type;
+      // array
+      if (clazz.isArray()) {
+        return getFullyQualifiedName(clazz.getComponentType(), runtime) + "[]";
+      }
+      // object
+      String name = clazz.getName();
+      if (!runtime) {
+        name = name.replace('$', '.');
+      }
+      return name;
+    }
+    // GenericArrayType
+    if (type instanceof GenericArrayType) {
+      GenericArrayType genericArrayType = (GenericArrayType) type;
+      return getFullyQualifiedName(genericArrayType.getGenericComponentType(), runtime) + "[]";
+    }
+    // ParameterizedType
+    if (type instanceof ParameterizedType) {
+      ParameterizedType parameterizedType = (ParameterizedType) type;
+      Type rawType = parameterizedType.getRawType();
+      // raw type
+      StringBuilder sb = new StringBuilder();
+      sb.append(getFullyQualifiedName(rawType, runtime));
+      // type arguments
+      sb.append("<");
+      boolean firstTypeArgument = true;
+      for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
+        if (!firstTypeArgument) {
+          sb.append(",");
+        }
+        firstTypeArgument = false;
+        sb.append(getFullyQualifiedName(typeArgument, runtime));
+      }
+      sb.append(">");
+      // done
+      return sb.toString();
+    }
+    // WildcardType
+    if (type instanceof WildcardType) {
+      WildcardType wildcardType = (WildcardType) type;
+      return "? extends " + getFullyQualifiedName(wildcardType.getUpperBounds()[0], runtime);
+    }
+    // TypeVariable
+    TypeVariable<?> typeVariable = (TypeVariable<?>) type;
+    return typeVariable.getName();
+  }
+
+  /**
+   * Appends fully qualified names of given parameter types (appends also <code>"()"</code>).
+   */
+  private static void appendParameterTypes(StringBuilder buffer, Type[] parameterTypes) {
+    buffer.append('(');
+    boolean firstParameter = true;
+    for (Type parameterType : parameterTypes) {
+      if (firstParameter) {
+        firstParameter = false;
+      } else {
+        buffer.append(',');
+      }
+      buffer.append(getFullyQualifiedName(parameterType, false));
+    }
+    buffer.append(')');
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Method
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return all declared {@link Method}'s, including protected and private.
+   */
+  public static Map<String, Method> getMethods(Class<?> clazz) {
+    Map<String, Method> methods = Maps.newHashMap();
+    // process classes
+    for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
+      for (Method method : c.getDeclaredMethods()) {
+        String signature = getMethodSignature(method);
+        if (!methods.containsKey(signature)) {
+          method.setAccessible(true);
+          methods.put(signature, method);
+        }
+      }
+    }
+    // process interfaces
+    for (Class<?> interfaceClass : clazz.getInterfaces()) {
+      for (Method method : interfaceClass.getDeclaredMethods()) {
+        String signature = getMethodSignature(method);
+        if (!methods.containsKey(signature)) {
+          method.setAccessible(true);
+          methods.put(signature, method);
+        }
+      }
+    }
+    // done
+    return methods;
+  }
+
+  /**
+   * @return signature for given {@link Method}. This signature is not same signature as in JVM or
+   *         JDT, just some string that unique identifies method in its {@link Class}.
+   */
+  public static String getMethodSignature(Method method) {
+    Assert.isNotNull(method);
+    return getMethodSignature(method.getName(), method.getParameterTypes());
+  }
+
+  /**
+   * Returns the signature of {@link Method} with given combination of name and parameter types.
+   * This signature is not same signature as in JVM or JDT, just some string that unique identifies
+   * method in its {@link Class}.
+   *
+   * @param name
+   *          the name of {@link Method}.
+   * @param parameterTypes
+   *          the types of {@link Method} parameters.
+   *
+   * @return signature of {@link Method}.
+   */
+  public static String getMethodSignature(String name, Type... parameterTypes) {
+    Assert.isNotNull(name);
+    Assert.isNotNull(parameterTypes);
+    //
+    StringBuilder buffer = new StringBuilder();
+    buffer.append(name);
+    appendParameterTypes(buffer, parameterTypes);
+    return buffer.toString();
+  }
+
+  private static final ClassMap<Map<String, Method>> m_getMethodBySignature = ClassMap.create();
+
+  /**
+   * Returns the {@link Method} defined in {@link Class}. This method can have any visibility, i.e.
+   * we can find even protected/private methods. Can return <code>null</code> if no method with
+   * given signature found.
+   *
+   * @param clazz
+   *          the {@link Class} to get method from it, or its superclass.
+   * @param signature
+   *          the signature of method in same format as {@link #getMethodSignature(Method)}.
+   *
+   * @return the {@link Method} for given signature, or <code>null</code> if no such method found.
+   */
+  public static Method getMethodBySignature(Class<?> clazz, String signature) {
+    Assert.isNotNull(clazz);
+    Assert.isNotNull(signature);
+    // prepare cache
+    Map<String, Method> cache = m_getMethodBySignature.get(clazz);
+    if (cache == null) {
+      cache = getMethods(clazz);
+      m_getMethodBySignature.put(clazz, cache);
+    }
+    // use cache
+    return cache.get(signature);
+  }
+
+  /**
+   * @return the {@link Object} result of invoking method with given signature.
+   */
+  public static Object invokeMethod(Object object, String signature, Object... arguments)
+      throws Exception {
+    Assert.isNotNull(object);
+    Assert.isNotNull(arguments);
+    // prepare class/object
+    Class<?> refClass = getRefClass(object);
+    Object refObject = getRefObject(object);
+    // prepare method
+    Method method = getMethodBySignature(refClass, signature);
+    Assert.isNotNull(method, "Can not find method " + signature + " in " + refClass);
+    // do invoke
+    try {
+      return method.invoke(refObject, arguments);
+    } catch (InvocationTargetException e) {
+      throw propagate(e.getCause());
+    }
+  }
+
+  /**
+   * Invokes method by name and parameter types.
+   *
+   * @param object
+   *          the object to call, may be {@link Class} for invoking static method.
+   * @param name
+   *          the name of method.
+   * @param parameterTypes
+   *          the types of parameters.
+   * @param arguments
+   *          the values of argument for invocation.
+   *
+   * @return the {@link Object} result of invoking method.
+   */
+  public static Object invokeMethod2(Object object,
+      String name,
+      Class<?>[] parameterTypes,
+      Object[] arguments) throws Exception {
+    Assert.equals(parameterTypes.length, arguments.length);
+    String signature = getMethodSignature(name, parameterTypes);
+    return invokeMethod(object, signature, arguments);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the {@link Class} of given {@link Object} or casted object, if it is {@link Class}
+   *         itself.
+   */
+  private static Class<?> getRefClass(Object object) {
+    return object instanceof Class<?> ? (Class<?>) object : object.getClass();
+  }
+
+  /**
+   * @return the {@link Object} that should be used as argument for {@link Field#get(Object)} and
+   *         {@link Method#invoke(Object, Object[])}.
+   */
+  private static Object getRefObject(Object object) {
+    return object instanceof Class<?> ? null : object;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Throwable propagation
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Helper class used in {@link #propagate(Throwable)}.
+   */
+  private static class ExceptionThrower {
+    private static Throwable throwable;
+
+    private ExceptionThrower() throws Throwable {
+      if (System.getProperty("wbp.ReflectionUtils.propagate().InstantiationException") != null) {
+        throw new InstantiationException();
+      }
+      if (System.getProperty("wbp.ReflectionUtils.propagate().IllegalAccessException") != null) {
+        throw new IllegalAccessException();
+      }
+      throw throwable;
+    }
+
+    public static synchronized void spit(Throwable t) {
+      if (System.getProperty("wbp.ReflectionUtils.propagate().dontThrow") == null) {
+        ExceptionThrower.throwable = t;
+        try {
+          ExceptionThrower.class.newInstance();
+        } catch (InstantiationException e) {
+        } catch (IllegalAccessException e) {
+        } finally {
+          ExceptionThrower.throwable = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * Propagates {@code throwable} as-is without any wrapping. This is trick.
+   *
+   * @return nothing will ever be returned; this return type is only for your convenience, to use
+   *         this method in "throw" statement.
+   */
+  public static RuntimeException propagate(Throwable throwable) {
+    if (System.getProperty("wbp.ReflectionUtils.propagate().forceReturn") == null) {
+      ExceptionThrower.spit(throwable);
+    }
+    return null;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java
new file mode 100644
index 0000000..f7cc09d
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java
@@ -0,0 +1,337 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.ui;
+
+import com.google.common.io.Closeables;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.wb.draw2d.IColorConstants;
+
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * Utilities for drawing on {@link GC}.
+ *
+ * @author scheglov_ke
+ * @coverage core.ui
+ */
+public class DrawUtils {
+  private static final String DOTS = "...";
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Drawing
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Draws given text clipped horizontally and centered vertically.
+   */
+  public static final void drawStringCV(GC gc, String text, int x, int y, int width, int height) {
+    Rectangle oldClipping = gc.getClipping();
+    try {
+      gc.setClipping(new Rectangle(x, y, width, height));
+      //
+      int textStartY = y + (height - gc.getFontMetrics().getHeight()) / 2;
+      gc.drawString(clipString(gc, text, width), x, textStartY, true);
+    } finally {
+      gc.setClipping(oldClipping);
+    }
+  }
+
+  /**
+   * Draws given text clipped or centered horizontally and centered vertically.
+   */
+  public static final void drawStringCHCV(GC gc, String text, int x, int y, int width, int height) {
+    int textStartY = y + (height - gc.getFontMetrics().getHeight()) / 2;
+    Point textSize = gc.stringExtent(text);
+    //
+    if (textSize.x > width) {
+      gc.drawString(clipString(gc, text, width), x, textStartY);
+    } else {
+      gc.drawString(text, x + (width - textSize.x) / 2, textStartY);
+    }
+  }
+
+  /**
+   * Draws image at given <code>x</code> and centered vertically.
+   */
+  public static final void drawImageCV(GC gc, Image image, int x, int y, int height) {
+    if (image != null) {
+      Rectangle imageBounds = image.getBounds();
+      gc.drawImage(image, x, y + (height - imageBounds.height) / 2);
+    }
+  }
+
+  /**
+   * Draws image at given <code>x</code> and centered vertically.
+   */
+  public static final void drawImageCHCV(GC gc, Image image, int x, int y, int width, int height) {
+    if (image != null) {
+      Rectangle imageBounds = image.getBounds();
+      int centerX = (width - imageBounds.width) / 2;
+      int centerY = y + (height - imageBounds.height) / 2;
+      gc.drawImage(image, x + centerX, centerY);
+    }
+  }
+
+  /**
+   * Draws {@link Image} on {@link GC} centered in given {@link Rectangle}. If {@link Image} is
+   * bigger that {@link Rectangle}, {@link Image} will be scaled down as needed with keeping
+   * proportions.
+   */
+  public static void drawScaledImage(GC gc, Image image, Rectangle targetRectangle) {
+    int imageWidth = image.getBounds().width;
+    int imageHeight = image.getBounds().height;
+    // prepare scaled image size
+    int newImageWidth;
+    int newImageHeight;
+    if (imageWidth <= targetRectangle.width && imageHeight <= targetRectangle.height) {
+      newImageWidth = imageWidth;
+      newImageHeight = imageHeight;
+    } else {
+      // prepare minimal scale
+      double k;
+      {
+        double k_w = targetRectangle.width / (double) imageWidth;
+        double k_h = targetRectangle.height / (double) imageHeight;
+        k = Math.min(k_w, k_h);
+      }
+      // calculate scaled image size
+      newImageWidth = (int) (imageWidth * k);
+      newImageHeight = (int) (imageHeight * k);
+    }
+    // draw image centered in target rectangle
+    int destX = targetRectangle.x + (targetRectangle.width - newImageWidth) / 2;
+    int destY = targetRectangle.y + (targetRectangle.height - newImageHeight) / 2;
+    gc.drawImage(image, 0, 0, imageWidth, imageHeight, destX, destY, newImageWidth, newImageHeight);
+  }
+
+  /**
+   * @return the string clipped to have width less than given. Clipping is done as trailing "...".
+   */
+  public static String clipString(GC gc, String text, int width) {
+    if (width <= 0) {
+      return "";
+    }
+    // check if text already fits in given width
+    if (gc.stringExtent(text).x <= width) {
+      return text;
+    }
+    // use average count of characters as base
+    int count = Math.min(width / gc.getFontMetrics().getAverageCharWidth(), text.length());
+    if (gc.stringExtent(text.substring(0, count) + DOTS).x > width) {
+      while (count > 0 && gc.stringExtent(text.substring(0, count) + DOTS).x > width) {
+        count--;
+      }
+    } else {
+      while (count < text.length() - 1
+          && gc.stringExtent(text.substring(0, count + 1) + DOTS).x < width) {
+        count++;
+      }
+    }
+    return text.substring(0, count) + DOTS;
+  }
+
+  /**
+   * Draws {@link String} in rectangle, wraps at any character (not by words).
+   */
+  public static void drawTextWrap(GC gc, String text, int x, int y, int width, int height) {
+    int y_ = y;
+    int x_ = x;
+    int lineHeight = 0;
+    for (int i = 0; i < text.length(); i++) {
+      String c = text.substring(i, i + 1);
+      Point extent = gc.stringExtent(c);
+      if (x_ + extent.x > x + width) {
+        y_ += lineHeight;
+        if (y_ > y + height) {
+          return;
+        }
+        x_ = x;
+      }
+      gc.drawText(c, x_, y_);
+      x_ += extent.x;
+      lineHeight = Math.max(lineHeight, extent.y);
+    }
+  }
+
+  /**
+   * Draws 3D highlight rectangle.
+   */
+  public static void drawHighlightRectangle(GC gc, int x, int y, int width, int height) {
+    int right = x + width - 1;
+    int bottom = y + height - 1;
+    //
+    Color oldForeground = gc.getForeground();
+    try {
+      gc.setForeground(IColorConstants.buttonLightest);
+      gc.drawLine(x, y, right, y);
+      gc.drawLine(x, y, x, bottom);
+      //
+      gc.setForeground(IColorConstants.buttonDarker);
+      gc.drawLine(right, y, right, bottom);
+      gc.drawLine(x, bottom, right, bottom);
+    } finally {
+      gc.setForeground(oldForeground);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Images
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the {@link Image} loaded relative to given {@link Class}.
+   */
+  public static Image loadImage(Class<?> clazz, String path) {
+    try {
+      URL resource = clazz.getResource(path);
+      if (resource != null) {
+        InputStream stream = resource.openStream();
+        try {
+          return new Image(null, stream);
+        } finally {
+          Closeables.closeQuietly(stream);
+        }
+      }
+    } catch (Throwable e) {
+    }
+    return null;
+  }
+
+  /**
+   * @return the thumbnail {@link Image} of required size for given "big" {@link Image}, centered or
+   *         scaled down.
+   */
+  public static Image getThubmnail(Image image,
+      int minWidth,
+      int minHeight,
+      int maxWidth,
+      int maxHeight) {
+    Rectangle imageBounds = image.getBounds();
+    int imageWidth = imageBounds.width;
+    int imageHeight = imageBounds.height;
+    if (imageWidth < minWidth && imageHeight < minHeight) {
+      // create "thumbnail" Image with required size
+      Image thumbnail = new Image(null, minWidth, minHeight);
+      GC gc = new GC(thumbnail);
+      try {
+        drawImageCHCV(gc, image, 0, 0, minWidth, minHeight);
+      } finally {
+        gc.dispose();
+      }
+      // recreate "thumbnail" Image with transparent pixel
+      try {
+        ImageData thumbnailData = thumbnail.getImageData();
+        thumbnailData.transparentPixel = thumbnailData.getPixel(0, 0);
+        return new Image(null, thumbnailData);
+      } finally {
+        thumbnail.dispose();
+      }
+    } else if (imageWidth <= maxWidth && imageHeight <= maxHeight) {
+      return new Image(null, image, SWT.IMAGE_COPY);
+    } else {
+      double kX = (double) maxWidth / imageWidth;
+      double kY = (double) maxHeight / imageHeight;
+      double k = Math.max(kX, kY);
+      int dWidth = (int) (imageWidth * k);
+      int dHeight = (int) (imageHeight * k);
+      ImageData scaledImageData = image.getImageData().scaledTo(dWidth, dHeight);
+      return new Image(null, scaledImageData);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Colors
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return new {@link Color} based on given {@link Color} and shifted on given value to make it
+   *         darker or lighter.
+   */
+  public static Color getShiftedColor(Color color, int delta) {
+    int r = Math.max(0, Math.min(color.getRed() + delta, 255));
+    int g = Math.max(0, Math.min(color.getGreen() + delta, 255));
+    int b = Math.max(0, Math.min(color.getBlue() + delta, 255));
+    return new Color(color.getDevice(), r, g, b);
+  }
+
+  /**
+   * @return <code>true</code> if the given <code>color</code> is dark.
+   */
+  public static boolean isDarkColor(Color c) {
+    int value =
+        (int) Math.sqrt(c.getRed()
+            * c.getRed()
+            * .241
+            + c.getGreen()
+            * c.getGreen()
+            * .691
+            + c.getBlue()
+            * c.getBlue()
+            * .068);
+    return value < 130;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Fonts
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the bold version of given {@link Font}.
+   */
+  public static Font getBoldFont(Font baseFont) {
+    FontData[] boldData = getModifiedFontData(baseFont, SWT.BOLD);
+    return new Font(Display.getCurrent(), boldData);
+  }
+
+  /**
+   * @return the italic version of given {@link Font}.
+   */
+  public static Font getBoldItalicFont(Font baseFont) {
+    FontData[] boldData = getModifiedFontData(baseFont, SWT.BOLD | SWT.ITALIC);
+    return new Font(Display.getCurrent(), boldData);
+  }
+
+  /**
+   * @return the italic version of given {@link Font}.
+   */
+  public static Font getItalicFont(Font baseFont) {
+    FontData[] boldData = getModifiedFontData(baseFont, SWT.ITALIC);
+    return new Font(Display.getCurrent(), boldData);
+  }
+
+  /**
+   * @return the array of {@link FontData} with the specified style.
+   */
+  private static FontData[] getModifiedFontData(Font baseFont, int style) {
+    FontData[] baseData = baseFont.getFontData();
+    FontData[] styleData = new FontData[baseData.length];
+    for (int i = 0; i < styleData.length; i++) {
+      FontData base = baseData[i];
+      styleData[i] = new FontData(base.getName(), base.getHeight(), base.getStyle() | style);
+    }
+    return styleData;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridDataFactory.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridDataFactory.java
new file mode 100644
index 0000000..4ccda40
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridDataFactory.java
@@ -0,0 +1,541 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.ui;
+
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * This class provides a convienient shorthand for creating and initializing GridData. This offers
+ * several benefits over creating GridData normal way:
+ * 
+ * <ul>
+ * <li>The same factory can be used many times to create several GridData instances</li>
+ * <li>The setters on GridDataFactory all return "this", allowing them to be chained</li>
+ * <li>GridDataFactory uses vector setters (it accepts Points), making it easy to set X and Y values
+ * together</li>
+ * </ul>
+ * 
+ * <p>
+ * GridDataFactory instances are created using one of the static methods on this class.
+ * </p>
+ * 
+ * <p>
+ * Example usage:
+ * </p>
+ * <code>
+ * 
+ * ////////////////////////////////////////////////////////////
+ * // Example 1: Typical grid data for a non-wrapping label
+ * 
+ *     // GridDataFactory version
+ *     GridDataFactory.fillDefaults().applyTo(myLabel);
+ * 
+ *     // Equivalent SWT version
+ *     GridData labelData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL);
+ *     myLabel.setLayoutData(labelData);
+ * 
+ * ///////////////////////////////////////////////////////////
+ * // Example 2: Typical grid data for a wrapping label
+ * 
+ *     // GridDataFactory version
+ *     GridDataFactory.fillDefaults()
+ *          .align(SWT.FILL, SWT.CENTER)
+ *    	    .hint(150, SWT.DEFAULT)
+ *    	    .grab(true, false)
+ *          .applyTo(wrappingLabel);
+ *      
+ *     // Equivalent SWT version
+ *     GridData wrappingLabelData = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_CENTER);
+ *     wrappingLabelData.minimumWidth = 1;
+ *     wrappingLabelData.widthHint = 150;
+ *     wrappingLabel.setLayoutData(wrappingLabelData);
+ * 
+ * //////////////////////////////////////////////////////////////
+ * // Example 3: Typical grid data for a scrollable control (a list box, tree, table, etc.)
+ * 
+ *     // GridDataFactory version
+ *     GridDataFactory.fillDefaults().grab(true, true).hint(150, 150).applyTo(listBox);
+ * 
+ *     // Equivalent SWT version
+ *     GridData listBoxData = new GridData(GridData.FILL_BOTH);
+ *     listBoxData.widthHint = 150;
+ *     listBoxData.heightHint = 150;
+ *     listBoxData.minimumWidth = 1;
+ *     listBoxData.minimumHeight = 1;
+ *     listBox.setLayoutData(listBoxData);
+ * 
+ * /////////////////////////////////////////////////////////////
+ * // Example 4: Typical grid data for a button
+ * 
+ *     // GridDataFactory version
+ *     Point preferredSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
+ *     Point hint = Geometry.max(LayoutConstants.getMinButtonSize(), preferredSize);
+ *     GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).hint(hint).applyTo(button);
+ * 
+ *     // Equivalent SWT version
+ *     Point preferredSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
+ *     Point hint = Geometry.max(LayoutConstants.getMinButtonSize(), preferredSize);
+ *     GridData buttonData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER);
+ *     buttonData.widthHint = hint.x;
+ *     buttonData.heightHint = hint.y;
+ *     button.setLayoutData(buttonData); 
+ * </code>
+ * 
+ * <p>
+ * IMPORTANT: WHEN ASSIGNING LAYOUT DATA TO A CONTROL, BE SURE TO USE
+ * gridDataFactory.applyTo(control) AND NEVER control.setLayoutData(gridDataFactory).
+ * </p>
+ * 
+ * @since 3.2
+ */
+public final class GridDataFactory {
+  private final Control m_control;
+  private final PixelConverter m_pixelConverter;
+  private final GridData m_data;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private GridDataFactory(Control control, GridData gridData) {
+    m_control = control;
+    m_pixelConverter = new PixelConverter(m_control);
+    //
+    m_data = gridData;
+    if (m_control.getLayoutData() != m_data) {
+      m_control.setLayoutData(m_data);
+    }
+  }
+
+  /**
+   * Creates new {@link GridDataFactory} with new {@link GridData}.
+   */
+  public static GridDataFactory create(Control control) {
+    return new GridDataFactory(control, new GridData());
+  }
+
+  /**
+   * Creates new {@link GridDataFactory} for modifying {@link GridData} already installed in
+   * control.
+   */
+  public static GridDataFactory modify(Control control) {
+    GridData gridData;
+    {
+      Object existingLayoutData = control.getLayoutData();
+      if (existingLayoutData instanceof GridData) {
+        gridData = (GridData) existingLayoutData;
+      } else {
+        gridData = new GridData();
+      }
+    }
+    return new GridDataFactory(control, gridData);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Span
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets the GridData span. The span controls how many cells are filled by the control.
+   * 
+   * @param hSpan
+   *          number of columns spanned by the control
+   * @param vSpan
+   *          number of rows spanned by the control
+   * @return this
+   */
+  public GridDataFactory span(int hSpan, int vSpan) {
+    m_data.horizontalSpan = hSpan;
+    m_data.verticalSpan = vSpan;
+    return this;
+  }
+
+  /**
+   * Sets the GridData span. The span controls how many cells are filled by the control.
+   * 
+   * @param hSpan
+   *          number of columns spanned by the control
+   * @return this
+   */
+  public GridDataFactory spanH(int hSpan) {
+    m_data.horizontalSpan = hSpan;
+    return this;
+  }
+
+  /**
+   * Sets the GridData span. The span controls how many cells are filled by the control.
+   * 
+   * @param vSpan
+   *          number of rows spanned by the control
+   * @return this
+   */
+  public GridDataFactory spanV(int vSpan) {
+    m_data.verticalSpan = vSpan;
+    return this;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Hint
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets the width and height hints. The width and height hints override the control's preferred
+   * size. If either hint is set to SWT.DEFAULT, the control's preferred size is used.
+   * 
+   * @param xHint
+   *          horizontal hint (pixels), or SWT.DEFAULT to use the control's preferred size
+   * @param yHint
+   *          vertical hint (pixels), or SWT.DEFAULT to use the control's preferred size
+   * @return this
+   */
+  public GridDataFactory hint(int xHint, int yHint) {
+    m_data.widthHint = xHint;
+    m_data.heightHint = yHint;
+    return this;
+  }
+
+  /**
+   * Sets hint in chars.
+   */
+  public GridDataFactory hintC(int xHintInChars, int yHintInChars) {
+    hintHC(xHintInChars);
+    hintVC(yHintInChars);
+    return this;
+  }
+
+  /**
+   * Sets the width hint.
+   * 
+   * @return this
+   */
+  public GridDataFactory hintH(int xHint) {
+    m_data.widthHint = xHint;
+    return this;
+  }
+
+  /**
+   * Sets the width hint to the minimum of current hint and given <code>otherHint</code>.
+   * 
+   * @return this
+   */
+  public GridDataFactory hintHMin(int otherHint) {
+    m_data.widthHint = Math.min(m_data.widthHint, otherHint);
+    return this;
+  }
+
+  /**
+   * Sets the width hint in chars.
+   * 
+   * @return this
+   */
+  public GridDataFactory hintHC(int hintInChars) {
+    return hintH(m_pixelConverter.convertWidthInCharsToPixels(hintInChars));
+  }
+
+  /**
+   * Sets the width hint.
+   * 
+   * @return this
+   */
+  public GridDataFactory hintHU(int hintInDLU) {
+    return hintH(m_pixelConverter.convertHorizontalDLUsToPixels(hintInDLU));
+  }
+
+  /**
+   * Sets the height hint.
+   * 
+   * @return this
+   */
+  public GridDataFactory hintV(int yHint) {
+    m_data.heightHint = yHint;
+    return this;
+  }
+
+  /**
+   * Sets the height hint in chars.
+   * 
+   * @return this
+   */
+  public GridDataFactory hintVC(int hintInChars) {
+    return hintV(m_pixelConverter.convertHeightInCharsToPixels(hintInChars));
+  }
+
+  /**
+   * Increments horizontal hint on given value.
+   * 
+   * @return this
+   */
+  public GridDataFactory hintHAdd(int increment) {
+    return hintV(m_data.widthHint + increment);
+  }
+
+  /**
+   * Increments vertical hint on given value.
+   * 
+   * @return this
+   */
+  public GridDataFactory hintVAdd(int increment) {
+    return hintV(m_data.heightHint + increment);
+  }
+
+  /**
+   * Sets the width and height hints. The width and height hints override the control's preferred
+   * size. If either hint is set to SWT.DEFAULT, the control's preferred size is used.
+   * 
+   * @param hint
+   *          size (pixels) to be used instead of the control's preferred size. If the x or y values
+   *          are set to SWT.DEFAULT, the control's computeSize() method will be used to obtain that
+   *          dimension of the preferred size.
+   * @return this
+   */
+  public GridDataFactory hint(Point hint) {
+    m_data.widthHint = hint.x;
+    m_data.heightHint = hint.y;
+    return this;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Minimum size
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public GridDataFactory minH(int minimumWidth) {
+    m_data.minimumWidth = minimumWidth;
+    return this;
+  }
+
+  public GridDataFactory minHC(int widthInChars) {
+    return minH(m_pixelConverter.convertWidthInCharsToPixels(widthInChars));
+  }
+
+  public GridDataFactory minV(int minimumHeight) {
+    m_data.minimumHeight = minimumHeight;
+    return this;
+  }
+
+  public GridDataFactory minVC(int heightInChars) {
+    return minV(m_pixelConverter.convertHeightInCharsToPixels(heightInChars));
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Alignment
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets the alignment of the control within its cell.
+   * 
+   * @param hAlign
+   *          horizontal alignment. One of SWT.BEGINNING, SWT.CENTER, SWT.END, or SWT.FILL.
+   * @param vAlign
+   *          vertical alignment. One of SWT.BEGINNING, SWT.CENTER, SWT.END, or SWT.FILL.
+   * @return this
+   */
+  public GridDataFactory align(int hAlign, int vAlign) {
+    m_data.horizontalAlignment = hAlign;
+    m_data.verticalAlignment = vAlign;
+    return this;
+  }
+
+  /**
+   * Sets the horizontal and vertical alignment to GridData.FILL.
+   */
+  public GridDataFactory fill() {
+    return align(GridData.FILL, GridData.FILL);
+  }
+
+  /**
+   * Sets the horizontal alignment of the control within its cell.
+   * 
+   * @param hAlign
+   *          horizontal alignment. One of SWT.BEGINNING, SWT.CENTER, SWT.END, or SWT.FILL.
+   * @return this
+   */
+  public GridDataFactory alignH(int hAlign) {
+    m_data.horizontalAlignment = hAlign;
+    return this;
+  }
+
+  /**
+   * Sets the horizontal alignment of the control to GridData.BEGINNING
+   * 
+   * @return this
+   */
+  public GridDataFactory alignHL() {
+    return alignH(GridData.BEGINNING);
+  }
+
+  /**
+   * Sets the horizontal alignment of the control to GridData.CENTER
+   * 
+   * @return this
+   */
+  public GridDataFactory alignHC() {
+    return alignH(GridData.CENTER);
+  }
+
+  /**
+   * Sets the horizontal alignment of the control to GridData.FILL
+   * 
+   * @return this
+   */
+  public GridDataFactory alignHF() {
+    return alignH(GridData.FILL);
+  }
+
+  /**
+   * Sets the horizontal alignment of the control to GridData.FILL
+   * 
+   * @return this
+   */
+  public GridDataFactory fillH() {
+    return alignHF();
+  }
+
+  /**
+   * Sets the horizontal alignment of the control to GridData.END
+   * 
+   * @return this
+   */
+  public GridDataFactory alignHR() {
+    return alignH(GridData.END);
+  }
+
+  /**
+   * Sets the vertical alignment of the control within its cell.
+   * 
+   * @param vAlign
+   *          vertical alignment. One of SWT.BEGINNING, SWT.CENTER, SWT.END, or SWT.FILL.
+   * @return this
+   */
+  public GridDataFactory alignV(int vAlign) {
+    m_data.verticalAlignment = vAlign;
+    return this;
+  }
+
+  /**
+   * Sets the vertical alignment of the control to GridData.BEGINNING
+   * 
+   * @return this
+   */
+  public GridDataFactory alignVT() {
+    return alignV(GridData.BEGINNING);
+  }
+
+  /**
+   * Sets the vertical alignment of the control to GridData.CENTER
+   * 
+   * @return this
+   */
+  public GridDataFactory alignVM() {
+    return alignV(GridData.CENTER);
+  }
+
+  /**
+   * Sets the vertical alignment of the control to GridData.FILL
+   * 
+   * @return this
+   */
+  public GridDataFactory alignVF() {
+    return alignV(GridData.FILL);
+  }
+
+  /**
+   * Sets the vertical alignment of the control to GridData.FILL
+   * 
+   * @return this
+   */
+  public GridDataFactory fillV() {
+    return alignVF();
+  }
+
+  /**
+   * Sets the vertical alignment of the control to GridData.END
+   * 
+   * @return this
+   */
+  public GridDataFactory alignVB() {
+    return alignV(GridData.END);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Indent
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets the indent of the control within the cell in pixels.
+   */
+  public GridDataFactory indentH(int hIndent) {
+    m_data.horizontalIndent = hIndent;
+    return this;
+  }
+
+  /**
+   * Sets the indent of the control within the cell in characters.
+   */
+  public GridDataFactory indentHC(int hIndent) {
+    m_data.horizontalIndent = m_pixelConverter.convertWidthInCharsToPixels(hIndent);
+    return this;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Grab
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Determines whether extra horizontal or vertical space should be allocated to this control's
+   * column when the layout resizes. If any control in the column is set to grab horizontal then the
+   * whole column will grab horizontal space. If any control in the row is set to grab vertical then
+   * the whole row will grab vertical space.
+   * 
+   * @param horizontal
+   *          true if the control's column should grow horizontally
+   * @param vertical
+   *          true if the control's row should grow vertically
+   * @return this
+   */
+  public GridDataFactory grab(boolean horizontal, boolean vertical) {
+    m_data.grabExcessHorizontalSpace = horizontal;
+    m_data.grabExcessVerticalSpace = vertical;
+    return this;
+  }
+
+  public GridDataFactory grabH() {
+    m_data.grabExcessHorizontalSpace = true;
+    return this;
+  }
+
+  public GridDataFactory grabV() {
+    m_data.grabExcessVerticalSpace = true;
+    return this;
+  }
+
+  public GridDataFactory grab() {
+    return grab(true, true);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Exclude
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public GridDataFactory exclude(boolean value) {
+    m_data.exclude = value;
+    return this;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridLayoutFactory.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridLayoutFactory.java
new file mode 100644
index 0000000..f6dc58e
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/GridLayoutFactory.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.ui;
+
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Layout;
+
+/**
+ * GridLayoutFactory provides a convenient shorthand for creating and initializing GridLayout.
+ *
+ * @author scheglov_ke
+ */
+public final class GridLayoutFactory {
+  private final GridLayout m_layout;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private GridLayoutFactory(Composite composite, GridLayout layout) {
+    m_layout = layout;
+    composite.setLayout(m_layout);
+  }
+
+  public static GridLayoutFactory create(Composite composite) {
+    return new GridLayoutFactory(composite, new GridLayout());
+  }
+
+  public static GridLayoutFactory modify(Composite composite) {
+    Layout layout = composite.getLayout();
+    if (layout instanceof GridLayout) {
+      return new GridLayoutFactory(composite, (GridLayout) layout);
+    }
+    return create(composite);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets number of columns in {@link GridLayout}.
+   */
+  public GridLayoutFactory columns(int numColumns) {
+    m_layout.numColumns = numColumns;
+    return this;
+  }
+
+  /**
+   * Specifies whether all columns in the layout will be forced to have the same width.
+   */
+  public GridLayoutFactory equalColumns() {
+    m_layout.makeColumnsEqualWidth = true;
+    return this;
+  }
+
+  /**
+   * Sets the horizontal margins.
+   */
+  public GridLayoutFactory marginsH(int margins) {
+    m_layout.marginWidth = margins;
+    return this;
+  }
+
+  /**
+   * Sets the vertical margins.
+   */
+  public GridLayoutFactory marginsV(int margins) {
+    m_layout.marginHeight = margins;
+    return this;
+  }
+
+  /**
+   * Sets the horizontal/vertical margins.
+   */
+  public GridLayoutFactory margins(int margins) {
+    m_layout.marginWidth = m_layout.marginHeight = margins;
+    return this;
+  }
+
+  /**
+   * Sets zero horizontal and vertical margins.
+   */
+  public GridLayoutFactory noMargins() {
+    m_layout.marginWidth = m_layout.marginHeight = 0;
+    return this;
+  }
+
+  /**
+   * Sets zero horizontal and vertical spacing.
+   */
+  public GridLayoutFactory noSpacing() {
+    m_layout.horizontalSpacing = m_layout.verticalSpacing = 0;
+    return this;
+  }
+
+  /**
+   * Sets horizontal spacing.
+   */
+  public GridLayoutFactory spacingH(int spacing) {
+    m_layout.horizontalSpacing = spacing;
+    return this;
+  }
+
+  /**
+   * Sets vertical spacing.
+   */
+  public GridLayoutFactory spacingV(int spacing) {
+    m_layout.verticalSpacing = spacing;
+    return this;
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/ImageImageDescriptor.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/ImageImageDescriptor.java
new file mode 100644
index 0000000..d5fc39f
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/ImageImageDescriptor.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.ui;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+
+/**
+ * Implementation of {@link ImageDescriptor} for {@link Image} instance.
+ * 
+ * @author scheglov_ke
+ * @coverage core.ui
+ */
+public final class ImageImageDescriptor extends ImageDescriptor {
+  private final Image m_Image;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public ImageImageDescriptor(Image image) {
+    m_Image = image;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // ImageDescriptor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public ImageData getImageData() {
+    return m_Image == null ? null : m_Image.getImageData();
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/PixelConverter.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/PixelConverter.java
new file mode 100644
index 0000000..1e3699f
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/PixelConverter.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.ui;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * Helper class for converting DLU and char size into pixels.
+ * 
+ * Based on code from JDT UI.
+ * 
+ * @author scheglov_ke
+ */
+public class PixelConverter {
+  private final FontMetrics fFontMetrics;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructors
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public PixelConverter(Control control) {
+    GC gc = new GC(control);
+    gc.setFont(control.getFont());
+    fFontMetrics = gc.getFontMetrics();
+    gc.dispose();
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Conversions
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * see org.eclipse.jface.dialogs.DialogPage#convertHeightInCharsToPixels(int)
+   */
+  public int convertHeightInCharsToPixels(int chars) {
+    return Dialog.convertHeightInCharsToPixels(fFontMetrics, chars);
+  }
+
+  /**
+   * see org.eclipse.jface.dialogs.DialogPage#convertHorizontalDLUsToPixels(int)
+   */
+  public int convertHorizontalDLUsToPixels(int dlus) {
+    return Dialog.convertHorizontalDLUsToPixels(fFontMetrics, dlus);
+  }
+
+  /**
+   * see org.eclipse.jface.dialogs.DialogPage#convertVerticalDLUsToPixels(int)
+   */
+  public int convertVerticalDLUsToPixels(int dlus) {
+    return Dialog.convertVerticalDLUsToPixels(fFontMetrics, dlus);
+  }
+
+  /**
+   * see org.eclipse.jface.dialogs.DialogPage#convertWidthInCharsToPixels(int)
+   */
+  public int convertWidthInCharsToPixels(int chars) {
+    return Dialog.convertWidthInCharsToPixels(fFontMetrics, chars);
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/UiUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/UiUtils.java
new file mode 100644
index 0000000..126efba
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/UiUtils.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.ui;
+
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Utilities for UI.
+ *
+ * @author scheglov_ke
+ */
+public class UiUtils {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Message dialogs
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Opens standard warning dialog.
+   */
+  public static void openWarning(Shell parent, String title, String message) {
+    MessageDialog dialog =
+        new MessageDialog(parent,
+            title,
+            null,
+            message,
+            MessageDialog.WARNING,
+            new String[]{IDialogConstants.OK_LABEL},
+            0);
+    dialog.open();
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/ResizableDialog.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/ResizableDialog.java
new file mode 100644
index 0000000..307c2f6
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/ResizableDialog.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.ui.dialogs;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+
+/**
+ * {@link Dialog} that remembers location/size between usage sessions.
+ * 
+ * @author scheglov_ke
+ * @coverage core.ui
+ */
+public abstract class ResizableDialog extends Dialog {
+  /**
+   * Key for accessing {@link Dialog} from its {@link Shell}.
+   */
+  public static final String KEY_DIALOG = "KEY_DIALOG";
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Internal constants
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private static final String X = "x";
+  private static final String Y = "y";
+  private static final String WIDTH = "width";
+  private static final String HEIGHT = "height";
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance fields
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private final AbstractUIPlugin m_plugin;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public ResizableDialog(Shell parentShell, AbstractUIPlugin plugin) {
+    super(parentShell);
+    m_plugin = plugin;
+    setShellStyle(getShellStyle() | SWT.RESIZE | SWT.MAX);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Size
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected Point getInitialSize() {
+    // track the current dialog bounds
+    installDialogBoundsTracker();
+    // answer the size from the previous incarnation
+    Point defaultSize = getDefaultSize();
+    if ((getShellStyle() & SWT.RESIZE) != 0) {
+      Rectangle oldBounds = loadBounds();
+      if (oldBounds != null) {
+        Rectangle displayBounds = getShell().getDisplay().getBounds();
+        int width = Math.min(displayBounds.width, Math.max(oldBounds.width, defaultSize.x));
+        int height = Math.min(displayBounds.height, Math.max(oldBounds.height, defaultSize.y));
+        return new Point(width, height);
+      }
+    }
+    // use default size
+    return defaultSize;
+  }
+
+  /**
+   * @return the default size of dialog.
+   */
+  protected Point getDefaultSize() {
+    return super.getInitialSize();
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Location
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected Point getInitialLocation(Point initialSize) {
+    Rectangle windowBounds;
+    {
+      Shell windowShell = m_plugin.getWorkbench().getActiveWorkbenchWindow().getShell();
+      windowBounds = windowShell.getBounds();
+    }
+    // answer the location from the previous incarnation
+    Rectangle bounds = loadBounds();
+    if (bounds != null) {
+      int x = bounds.x;
+      int y = bounds.y;
+      int maxX = windowBounds.x + windowBounds.width - initialSize.x;
+      int maxY = windowBounds.y + windowBounds.height - initialSize.y;
+      if (x > maxX) {
+        x = maxX;
+      }
+      if (y > maxY) {
+        y = maxY;
+      }
+      if (x < windowBounds.x) {
+        x = windowBounds.x;
+      }
+      if (y < windowBounds.y) {
+        y = windowBounds.y;
+      }
+      return new Point(x, y);
+    }
+    // default location - centered on workbench window
+    int x = windowBounds.x + (windowBounds.width - initialSize.x) / 2;
+    int y = windowBounds.y + (windowBounds.height - initialSize.y) / 2;
+    return new Point(x, y);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Bounds
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Loads bounds from {@link IDialogSettings}.
+   */
+  private Rectangle loadBounds() {
+    IDialogSettings settings = getDialogSettings();
+    try {
+      return new Rectangle(settings.getInt(X),
+          settings.getInt(Y),
+          settings.getInt(WIDTH),
+          settings.getInt(HEIGHT));
+    } catch (NumberFormatException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Saves bounds to {@link IDialogSettings}.
+   */
+  private void saveBounds(Rectangle bounds) {
+    IDialogSettings settings = getDialogSettings();
+    settings.put(X, bounds.x);
+    settings.put(Y, bounds.y);
+    settings.put(WIDTH, bounds.width);
+    settings.put(HEIGHT, bounds.height);
+  }
+
+  /**
+   * @return the {@link IDialogSettings} for this dialog with this type.
+   */
+  protected IDialogSettings getDialogSettings() {
+    IDialogSettings settings = m_plugin.getDialogSettings();
+    String sectionName = getDialogSettingsSectionName();
+    if (settings.getSection(sectionName) == null) {
+      return settings.addNewSection(sectionName);
+    }
+    return settings.getSection(sectionName);
+  }
+
+  /**
+   * @return the name of section for dialog specific bounds. By default uses name of {@link Class},
+   *         but if same dialog is used for displaying different content, then may be overridden.
+   */
+  protected String getDialogSettingsSectionName() {
+    return getClass().getName();
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Size tracking
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected Rectangle cachedBounds;
+
+  private void installDialogBoundsTracker() {
+    getShell().addControlListener(new ControlListener() {
+      public void controlMoved(ControlEvent e) {
+        cachedBounds = getShell().getBounds();
+      }
+
+      public void controlResized(ControlEvent e) {
+        cachedBounds = getShell().getBounds();
+      }
+    });
+  }
+
+  @Override
+  public boolean close() {
+    boolean shellMaximized = getShell().getMaximized();
+    boolean closed = super.close();
+    if (closed && !shellMaximized && cachedBounds != null) {
+      saveBounds(cachedBounds);
+    }
+    return closed;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Shell
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  protected void configureShell(Shell newShell) {
+    super.configureShell(newShell);
+    newShell.setData(KEY_DIALOG, this);
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/StringsDialog.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/StringsDialog.java
new file mode 100644
index 0000000..745bcc3
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/StringsDialog.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.ui.dialogs;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
+import org.eclipse.wb.internal.core.utils.execution.RunnableObjectEx;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.util.List;
+
+/**
+ * The dialog for editing array of {@link String}'s.
+ *
+ * @author scheglov_ke
+ * @coverage core.ui
+ */
+public class StringsDialog extends TextDialog {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public StringsDialog(Shell parentShell,
+      AbstractUIPlugin plugin,
+      String titleText,
+      String headerText,
+      String footerText) {
+    super(parentShell, plugin, titleText, headerText, footerText);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Items
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets the items to edit.
+   */
+  public void setItems(String[] items) {
+    setText(Joiner.on('\n').join(items));
+  }
+
+  /**
+   * @return the edited items.
+   */
+  public String[] getItems() {
+    return ExecutionUtils.runObjectLog(new RunnableObjectEx<String[]>() {
+      @Override
+    public String[] runObject() throws Exception {
+        List<String> strings = Lists.newArrayList();
+        BufferedReader br = new BufferedReader(new StringReader(getText()));
+        while (true) {
+          String s = br.readLine();
+          if (s == null) {
+            break;
+          }
+          strings.add(s);
+        }
+        return strings.toArray(new String[strings.size()]);
+      }
+    }, new String[0]);
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/TextDialog.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/TextDialog.java
new file mode 100644
index 0000000..320fa32
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/dialogs/TextDialog.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.utils.ui.dialogs;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.eclipse.wb.internal.core.utils.ui.GridDataFactory;
+import org.eclipse.wb.internal.core.utils.ui.GridLayoutFactory;
+
+/**
+ * The dialog for editing multiline text.
+ *
+ * @author scheglov_ke
+ * @coverage core.ui
+ */
+public class TextDialog extends ResizableDialog {
+  private final String m_titleText;
+  private final String m_headerText;
+  private final String m_footerText;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public TextDialog(Shell parentShell,
+      AbstractUIPlugin plugin,
+      String titleText,
+      String headerText,
+      String footerText) {
+    super(parentShell, plugin);
+    m_titleText = titleText;
+    m_headerText = headerText;
+    m_footerText = footerText;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Text
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private String m_text;
+
+  /**
+   * Sets the text to edit.
+   */
+  public final void setText(String text) {
+    m_text = text;
+  }
+
+  /**
+   * @return the edited text.
+   */
+  public final String getText() {
+    return m_text;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // GUI
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  protected Text m_textWidget;
+
+  @Override
+  protected Control createDialogArea(Composite parent) {
+    Composite area = (Composite) super.createDialogArea(parent);
+    GridLayoutFactory.create(area);
+    // header
+    new Label(area, SWT.NONE).setText(m_headerText);
+    // Text widget
+    {
+      m_textWidget = new Text(area, SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+      GridDataFactory.create(m_textWidget).grab().fill().hintVC(10);
+      m_textWidget.setText(m_text);
+      // handle Ctrl+Enter as OK
+      m_textWidget.addKeyListener(new KeyAdapter() {
+        @Override
+        public void keyPressed(KeyEvent e) {
+          if (e.stateMask == SWT.CTRL && e.keyCode == SWT.CR) {
+            okPressed();
+          }
+        }
+      });
+    }
+    // footer
+    new Label(area, SWT.NONE).setText(m_footerText);
+    //
+    return area;
+  }
+
+  @Override
+  protected void configureShell(Shell newShell) {
+    super.configureShell(newShell);
+    newShell.setText(m_titleText);
+  }
+
+  @Override
+  protected void okPressed() {
+    m_text = m_textWidget.getText();
+    super.okPressed();
+  }
+}