aboutsummaryrefslogtreecommitdiffstats
path: root/eclipse/plugins/com.android.ide.eclipse.adt
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-03-20 11:50:57 -0700
committerTor Norbye <tnorbye@google.com>2012-04-03 07:55:09 -0700
commita7621238bf0202419677380ee3a268142358df83 (patch)
tree0bde7f60b2124e21ca825e1ac3ef9bad50426f9a /eclipse/plugins/com.android.ide.eclipse.adt
parentb0c819f896b3c579633849e3fa674a30e6978dd7 (diff)
downloadsdk-a7621238bf0202419677380ee3a268142358df83.zip
sdk-a7621238bf0202419677380ee3a268142358df83.tar.gz
sdk-a7621238bf0202419677380ee3a268142358df83.tar.bz2
Add support for the WindowBuilder Property Sheet
The WindowBuilder propertysheet has been extracted and added as a library in external/eclipse-windowbuilder/. This changeset removes the old propertysheet code (which used the builtin Eclipse property sheet page), and replaces it with the WindowBuilder one, along with new code to aggregate the properties into some categories, as well as tagging some of the properties as advanced. (This was computed by running the same analysis scripts used to produce the most-frequent attributes (sdk/attribute_stats) and instead computing which attributes are used very infrequently or not at all in some representative sample code.) The WindowBuilder propertysheet gives us the following new features: - Highlighting (bold) of important attributes - Masking (and when included, shown in gray italic) of advanced attributes - "Complex" attributes with nesting, used to for example aggregate all the layout parameters into a single node, and the margin layout attributes within those - Tooltips over the attribute names, not values, so they never obscure content In addition, this changeset adds custom implementations of properties, property editors and property dialogs for the core Android property types (XML strings, flags and booleans), which adds the following new features: - Preview rendering of color and image resources inline - Display of -default- attributes (those not specified in XML) using the layoutlib facility getDefaultProperties() to render the implied attributes. For example, if you look at a Button, it will show you that the implied value of "Text Color Link" is "@android:color/holo_blue_light" even though it is not set. NOTE: This only happens for attributes that were actually queried by the widget during rendering. Attributes that are not used by the widget have no (displayed) value. Thus, EditText-specific attributes in a TextView are not shown when a non-EditText TextView is selected. - Evaluation of the attributes. In the above example, in addition to showing @android:color/holo_blue_light, it will chase down the value of this to for example render a blue square next to the value. For drawables it will render a thumbnail, and for String resources it will display the actual value in parentheses. - Field completion in text fields, completing all resource strings (@string, @android:string, etc), as well as flag values. Enum values are chosen in a dropdown. - Checkbox support for boolean values, allowing you to click through the three values true, false and null. - Our custom version of the Property Sheet Page allows you to expand/collapse all properties, and it also has an option letting you switch between Alphabetical Sort (where all attributes are in a flat table, sorted alphabetically by property value), or hierarchical sorted "by category". Currently the categories are simply the defining views, plus 2 more (layout parameters and deprecated attributes). When we get more metadata, it would be nice to switch these to more logical categories, such as "text", "scrolling", "focus", etc. (There is some preliminary support for this in the code, but since the defining-view categories seem to work better those are used instead right now.) Change-Id: Ie4959a3a2c36c083dcc1ba19a70f24b33739fe2f
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/.classpath1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF3
-rw-r--r--[-rwxr-xr-x]eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_page_design.pngbin445 -> 445 bytes
-rw-r--r--[-rwxr-xr-x]eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_page_source.pngbin488 -> 488 bytes
-rw-r--r--[-rwxr-xr-x]eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_palette.pngbin430 -> 430 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/filter_advanced_properties.pngbin0 -> 331 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/goto_definition.pngbin0 -> 241 bytes
-rw-r--r--[-rwxr-xr-x]eclipse/plugins/com.android.ide.eclipse.adt/icons/match_multiple.pngbin256 -> 256 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/properties_default.pngbin0 -> 584 bytes
-rw-r--r--[-rwxr-xr-x]eclipse/plugins/com.android.ide.eclipse.adt/icons/sdk_manager.pngbin219 -> 219 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/sort_alpha.pngbin0 -> 277 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java35
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java26
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/XmlEditorMultiOutline.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java61
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java17
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java19
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java59
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PropertySheetPage.java232
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java19
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java72
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java31
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/PaletteMetadataDescriptor.java1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/BooleanXmlPropertyEditor.java118
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/EnumXmlPropertyEditor.java77
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagValueCompleter.java80
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagXmlPropertyDialog.java212
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java688
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyMetadata.java329
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertySheetPage.java367
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java164
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/StringXmlPropertyDialog.java31
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlProperty.java265
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyComposite.java118
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java428
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java22
41 files changed, 3192 insertions, 302 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
index f874a9e..a3376bd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
@@ -14,6 +14,7 @@
<classpathentry kind="lib" path="libs/assetstudio.jar" sourcepath="/assetstudio"/>
<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
<classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/>
+ <classpathentry kind="var" path="ANDROID_SRC/sdk/eclipse/plugins/com.android.ide.eclipse.adt/libs/propertysheet.jar" sourcepath="/ANDROID_SRC/external/eclipse-windowbuilder/propertysheet/src"/>
<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/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/lombok-ast/src.zip"/>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
index 0b40004..dcef3c9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
@@ -15,7 +15,8 @@ Bundle-ClassPath: .,
libs/lint_checks.jar,
libs/lombok-ast-0.2.jar,
libs/asm-4.0.jar,
- libs/asm-tree-4.0.jar
+ libs/asm-tree-4.0.jar,
+ libs/propertysheet.jar
Bundle-Activator: com.android.ide.eclipse.adt.AdtPlugin
Bundle-Vendor: The Android Open Source Project
Require-Bundle: com.android.ide.eclipse.base,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_page_design.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_page_design.png
index a19f3b0..a19f3b0 100755..100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_page_design.png
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_page_design.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_page_source.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_page_source.png
index 874cc1e..874cc1e 100755..100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_page_source.png
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_page_source.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_palette.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_palette.png
index c682f57..c682f57 100755..100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_palette.png
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/editor_palette.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/filter_advanced_properties.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/filter_advanced_properties.png
new file mode 100644
index 0000000..5f5b078
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/filter_advanced_properties.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/goto_definition.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/goto_definition.png
new file mode 100644
index 0000000..daac537
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/goto_definition.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/match_multiple.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/match_multiple.png
index 79ffc2d..79ffc2d 100755..100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/icons/match_multiple.png
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/match_multiple.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/properties_default.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/properties_default.png
new file mode 100644
index 0000000..5696a31
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/properties_default.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/sdk_manager.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/sdk_manager.png
index 08ffda8..08ffda8 100755..100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/icons/sdk_manager.png
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/sdk_manager.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/sort_alpha.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/sort_alpha.png
new file mode 100644
index 0000000..112e05b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/sort_alpha.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index f6fe09f..f8f6311 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -16,6 +16,11 @@
package com.android.ide.eclipse.adt;
+import static com.android.sdklib.SdkConstants.CURRENT_PLATFORM;
+import static com.android.sdklib.SdkConstants.PLATFORM_DARWIN;
+import static com.android.sdklib.SdkConstants.PLATFORM_LINUX;
+import static com.android.sdklib.SdkConstants.PLATFORM_WINDOWS;
+
import com.android.AndroidConstants;
import com.android.ide.common.log.ILogger;
import com.android.ide.common.resources.ResourceFile;
@@ -94,6 +99,7 @@ import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.texteditor.AbstractTextEditor;
+import org.eclipse.wb.internal.core.DesignerPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -257,6 +263,14 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
// load preferences.
AdtPrefs.getPrefs().loadValues(null /*event*/);
+ // initialize property-sheet library
+ DesignerPlugin.initialize(
+ this,
+ PLUGIN_ID,
+ CURRENT_PLATFORM == PLATFORM_WINDOWS,
+ CURRENT_PLATFORM == PLATFORM_DARWIN,
+ CURRENT_PLATFORM == PLATFORM_LINUX);
+
// initialize editors
startEditors();
@@ -286,6 +300,8 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
stopEditors();
IncludeFinder.stop();
+ DesignerPlugin.dispose();
+
mRed.dispose();
synchronized (AdtPlugin.class) {
sPlugin = null;
@@ -668,18 +684,19 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
}
/**
- * Reads the contents of an {@link InputStreamReader} and return it as a String
+ * Reads the contents of a {@link Reader} and return it as a String. This
+ * method will close the input reader.
*
- * @param inputStream the input stream to be read from
- * @return the String read from the stream, or null if there was an error
+ * @param reader the reader to be read from
+ * @return the String read from reader, or null if there was an error
*/
- public static String readFile(Reader inputStream) {
- BufferedReader reader = null;
+ public static String readFile(Reader reader) {
+ BufferedReader bufferedReader = null;
try {
- reader = new BufferedReader(inputStream);
+ bufferedReader = new BufferedReader(reader);
StringBuilder sb = new StringBuilder(2000);
while (true) {
- int c = reader.read();
+ int c = bufferedReader.read();
if (c == -1) {
return sb.toString();
} else {
@@ -690,8 +707,8 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
// pass -- ignore files we can't read
} finally {
try {
- if (reader != null) {
- reader.close();
+ if (bufferedReader != null) {
+ bufferedReader.close();
}
} catch (IOException e) {
AdtPlugin.log(e, "Can't read input stream"); //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index 08bb7d8..7e2a44d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -103,6 +103,32 @@ public class AdtUtils {
}
/**
+ * Returns true if the given string starts with the given prefix, using a
+ * case-insensitive comparison.
+ *
+ * @param string the full string to be checked
+ * @param prefix the prefix to be checked for
+ * @return true if the string case-insensitively starts with the given prefix
+ */
+ public static boolean startsWithIgnoreCase(String string, String prefix) {
+ return string.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length());
+ }
+
+ /**
+ * Returns true if the given string starts at the given offset with the
+ * given prefix, case insensitively.
+ *
+ * @param string the full string to be checked
+ * @param offset the offset in the string to start looking
+ * @param prefix the prefix to be checked for
+ * @return true if the string case-insensitively starts at the given offset
+ * with the given prefix
+ */
+ public static boolean startsWith(String string, int offset, String prefix) {
+ return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length());
+ }
+
+ /**
* Strips the whitespace from the given string
*
* @param string the string to be cleaned up
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java
index 22aa687..0a12ba4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java
@@ -373,5 +373,4 @@ public class IconFactory {
return data;
}
}
-
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/XmlEditorMultiOutline.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/XmlEditorMultiOutline.java
index 4ccab2d..61db9f3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/XmlEditorMultiOutline.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/XmlEditorMultiOutline.java
@@ -110,7 +110,7 @@ public class XmlEditorMultiOutline extends Page implements IContentOutlinePage,
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
if (mListeners == null) {
- mListeners = new ArrayList<ISelectionChangedListener>();
+ mListeners = new ArrayList<ISelectionChangedListener>(2);
}
mListeners.add(listener);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
index 20096f4..b00656e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
@@ -40,6 +40,7 @@ import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.La
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE;
+import com.android.annotations.NonNull;
import com.android.ide.common.api.IAttributeInfo.Format;
import com.android.ide.common.resources.platform.AttributeInfo;
import com.android.ide.eclipse.adt.AdtConstants;
@@ -308,6 +309,66 @@ public final class DescriptorsUtils {
}
/**
+ * Similar to {@link #prettyAttributeUiName(String)}, but it will capitalize
+ * all words, not just the first one.
+ * <p/>
+ * The original xml name starts with a lower case and is camel-case, e.g.
+ * "maxWidthForView". The corresponding return value is
+ * "Max Width For View".
+ *
+ * @param name the attribute name, which should be a camel case name, e.g.
+ * "maxWidth"
+ * @return the corresponding display name, e.g. "Max Width"
+ */
+ @NonNull
+ public static String capitalize(@NonNull String name) {
+ if (name.isEmpty()) {
+ return name;
+ }
+ StringBuilder buf = new StringBuilder(2 * name.length());
+
+ char c = name.charAt(0);
+ // Use upper case initial letter
+ buf.append(Character.toUpperCase(c));
+ int len = name.length();
+ for (int i = 1; i < len; i++) {
+ c = name.charAt(i);
+ if (Character.isUpperCase(c)) {
+ // Break camel case into separate words
+ buf.append(' ');
+ // Use a lower case initial letter for the next word, except if the
+ // word is solely X, Y or Z.
+ buf.append(c);
+ } else if (c == '_') {
+ buf.append(' ');
+ if (i < len -1 && Character.isLowerCase(name.charAt(i + 1))) {
+ buf.append(Character.toUpperCase(name.charAt(i + 1)));
+ i++;
+ }
+ } else {
+ buf.append(c);
+ }
+ }
+
+ name = buf.toString();
+
+ // Replace these acronyms by upper-case versions
+ // - (?<=^| ) means "if preceded by a space or beginning of string"
+ // - (?=$| ) means "if followed by a space or end of string"
+ if (name.contains("Sdk")) {
+ name = name.replaceAll("(?<=^| )Sdk(?=$| )", "SDK");
+ }
+ if (name.contains("Uri")) {
+ name = name.replaceAll("(?<=^| )Uri(?=$| )", "URI");
+ }
+ if (name.contains("Ime")) {
+ name = name.replaceAll("(?<=^| )Ime(?=$| )", "IME");
+ }
+
+ return name;
+ }
+
+ /**
* Formats the javadoc tooltip to be usable in a tooltip.
*/
public static String formatTooltip(String javadoc) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
index df39a3d..5f2b79b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
@@ -36,8 +36,9 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorP
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutActionBar;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.OutlinePage;
-import com.android.ide.eclipse.adt.internal.editors.layout.gle2.PropertySheetPage;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionManager;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
+import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertySheetPage;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
@@ -54,6 +55,8 @@ import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
@@ -476,6 +479,16 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=1917
if (mMultiOutline == null || mMultiOutline.isDisposed()) {
mMultiOutline = new XmlEditorMultiOutline();
+ mMultiOutline.addSelectionChangedListener(new ISelectionChangedListener() {
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ ISelection selection = event.getSelection();
+ getEditor().getSite().getSelectionProvider().setSelection(selection);
+ SelectionManager manager =
+ mGraphicalEditor.getCanvasControl().getSelectionManager();
+ manager.setSelection(selection);
+ }
+ });
updateOutline(getEditor().getActivePageInstance());
}
@@ -484,7 +497,7 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
if (IPropertySheetPage.class == adapter && mGraphicalEditor != null) {
if (mPropertyPage == null) {
- mPropertyPage = new PropertySheetPage();
+ mPropertyPage = new PropertySheetPage(mGraphicalEditor);
}
return mPropertyPage;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java
index 10e1a4d..2133abd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java
@@ -305,8 +305,6 @@ public final class LayoutDescriptors implements IDescriptorProvider {
AttributeInfo[] attrList = link.getAttributes();
if (attrList.length > 0) {
attributeSources.add(link.getFullClassName());
- attributes.add(new SeparatorAttributeDescriptor(
- String.format("Attributes from %1$s", link.getShortClassName())));
DescriptorsUtils.appendAttributes(attributes,
null, // elementName
SdkConstants.NS_RESOURCES,
@@ -321,28 +319,11 @@ public final class LayoutDescriptors implements IDescriptorProvider {
LayoutParamsInfo layoutParams = info.getLayoutData();
for(; layoutParams != null; layoutParams = layoutParams.getSuperClass()) {
- boolean needSeparator = true;
for (AttributeInfo attrInfo : layoutParams.getAttributes()) {
if (DescriptorsUtils.containsAttribute(layoutAttributes,
SdkConstants.NS_RESOURCES, attrInfo)) {
continue;
}
- if (needSeparator) {
- ViewClassInfo viewLayoutClass = layoutParams.getViewLayoutClass();
- String title;
- String shortClassName = viewLayoutClass.getShortClassName();
- if (layoutParams.getShortClassName().equals(
- SdkConstants.CLASS_NAME_LAYOUTPARAMS)) {
- title = String.format("Layout Attributes from %1$s",
- shortClassName);
- } else {
- title = String.format("Layout Attributes from %1$s (%2$s)",
- shortClassName,
- layoutParams.getShortClassName());
- }
- layoutAttributes.add(new SeparatorAttributeDescriptor(title));
- needSeparator = false;
- }
DescriptorsUtils.appendAttribute(layoutAttributes,
null, // elementName
SdkConstants.NS_RESOURCES,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
index dd103c5..c8497da 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
@@ -21,6 +21,8 @@ import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE_V7;
import static com.android.ide.common.layout.LayoutConstants.GESTURE_OVERLAY_VIEW;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.api.Margins;
import com.android.ide.common.api.Rect;
import com.android.ide.common.layout.GridLayoutRule;
@@ -70,8 +72,7 @@ public class CanvasViewInfo implements IPropertySource {
/**
* Minimal size of the selection, in case an empty view or layout is selected.
*/
- private static final int SELECTION_MIN_SIZE = 6;
-
+ public static final int SELECTION_MIN_SIZE = 6;
private final Rectangle mAbsRect;
private final Rectangle mSelectionRect;
@@ -121,17 +122,21 @@ public class CanvasViewInfo implements IPropertySource {
*
* @return the bounding box in absolute coordinates
*/
+ @NonNull
public Rectangle getAbsRect() {
return mAbsRect;
}
- /*
- * Returns the absolute selection bounds of the view info as a rectangle.
- * The selection bounds will always have a size greater or equal to
- * {@link #SELECTION_MIN_SIZE}.
- * The width/height is inclusive (i.e. width = right-left-1).
- * This is in absolute "screen" coordinates (relative to the rendered bitmap).
- */
+ /**
+ * Returns the absolute selection bounds of the view info as a rectangle.
+ * The selection bounds will always have a size greater or equal to
+ * {@link #SELECTION_MIN_SIZE}.
+ * The width/height is inclusive (i.e. width = right-left-1).
+ * This is in absolute "screen" coordinates (relative to the rendered bitmap).
+ *
+ * @return the absolute selection bounds
+ */
+ @NonNull
public Rectangle getSelectionRect() {
return mSelectionRect;
}
@@ -141,6 +146,7 @@ public class CanvasViewInfo implements IPropertySource {
* @return An {@link UiViewElementNode} that uniquely identifies the object in the XML model.
* @see ViewInfo#getCookie()
*/
+ @Nullable
public UiViewElementNode getUiViewNode() {
return mUiViewNode;
}
@@ -151,6 +157,7 @@ public class CanvasViewInfo implements IPropertySource {
*
* @return the parent {@link CanvasViewInfo}, which can be null
*/
+ @Nullable
public CanvasViewInfo getParent() {
return mParent;
}
@@ -162,6 +169,7 @@ public class CanvasViewInfo implements IPropertySource {
*
* @return the children, never null
*/
+ @NonNull
public List<CanvasViewInfo> getChildren() {
return mChildren;
}
@@ -171,6 +179,7 @@ public class CanvasViewInfo implements IPropertySource {
* children of a {@code <merge>} tag included into a separate layout, return the
* "primary" view, the first view that is rendered
*/
+ @Nullable
private CanvasViewInfo getPrimaryNodeSibling() {
if (mNodeSiblings == null || mNodeSiblings.size() == 0) {
return null;
@@ -200,6 +209,7 @@ public class CanvasViewInfo implements IPropertySource {
*
* @return a non-empty list of siblings (including this), or null
*/
+ @Nullable
public List<CanvasViewInfo> getNodeSiblings() {
return mNodeSiblings;
}
@@ -218,6 +228,7 @@ public class CanvasViewInfo implements IPropertySource {
* @return list of {@link CanvasViewInfo} objects that are children of this view,
* never null
*/
+ @NonNull
public List<CanvasViewInfo> getUniqueChildren() {
boolean haveHidden = false;
@@ -261,10 +272,7 @@ public class CanvasViewInfo implements IPropertySource {
* @param potentialParent the view info to check
* @return true if the given info is a parent of this view
*/
- public boolean isParent(CanvasViewInfo potentialParent) {
- if (potentialParent == null) {
-
- }
+ public boolean isParent(@NonNull CanvasViewInfo potentialParent) {
CanvasViewInfo p = mParent;
while (p != null) {
if (p == potentialParent) {
@@ -281,10 +289,11 @@ public class CanvasViewInfo implements IPropertySource {
* Experience shows this is the full qualified Java name of the View.
* TODO: Rename this method to getFqcn.
*
- * @return the name of the view info, or null
+ * @return the name of the view info
*
* @see ViewInfo#getClassName()
*/
+ @NonNull
public String getName() {
return mName;
}
@@ -293,10 +302,16 @@ public class CanvasViewInfo implements IPropertySource {
* Returns the View object associated with the {@link CanvasViewInfo}.
* @return the view object or null.
*/
+ @Nullable
public Object getViewObject() {
return mViewObject;
}
+ /**
+ * Returns the baseline of this object, or -1 if it does not support a baseline
+ *
+ * @return the baseline or -1
+ */
public int getBaseline() {
if (mViewInfo != null) {
int baseline = mViewInfo.getBaseLine();
@@ -313,6 +328,7 @@ public class CanvasViewInfo implements IPropertySource {
*
* @return the {@link Margins} for this {@link CanvasViewInfo}
*/
+ @Nullable
public Margins getMargins() {
if (mViewInfo != null) {
int leftMargin = mViewInfo.getLeftMargin();
@@ -331,6 +347,7 @@ public class CanvasViewInfo implements IPropertySource {
}
// ---- Implementation of IPropertySource
+ // TODO: Get rid of this once the old propertysheet implementation is fully gone
@Override
public Object getEditableValue() {
@@ -390,6 +407,7 @@ public class CanvasViewInfo implements IPropertySource {
*
* @return The XML node corresponding to this info object, or null
*/
+ @Nullable
public Node getXmlNode() {
UiViewElementNode uiView = getUiViewNode();
if (uiView != null) {
@@ -480,7 +498,7 @@ public class CanvasViewInfo implements IPropertySource {
*
* @param exploded New value of the exploded property to mark this info with.
*/
- /* package */ void setExploded(boolean exploded) {
+ void setExploded(boolean exploded) {
this.mExploded = exploded;
}
@@ -489,7 +507,8 @@ public class CanvasViewInfo implements IPropertySource {
*
* @return A {@link SimpleElement} wrapping this info.
*/
- /* package */ SimpleElement toSimpleElement() {
+ @NonNull
+ SimpleElement toSimpleElement() {
UiViewElementNode uiNode = getUiViewNode();
@@ -538,6 +557,7 @@ public class CanvasViewInfo implements IPropertySource {
* @return the layout url attribute value for the surrounding include tag, or null if
* not applicable
*/
+ @Nullable
public String getIncludeUrl() {
CanvasViewInfo curr = this;
while (curr != null) {
@@ -568,12 +588,12 @@ public class CanvasViewInfo implements IPropertySource {
}
/** Adds the given {@link CanvasViewInfo} as a new last child of this view */
- private void addChild(CanvasViewInfo child) {
+ private void addChild(@NonNull CanvasViewInfo child) {
mChildren.add(child);
}
/** Adds the given {@link CanvasViewInfo} as a child at the given index */
- private void addChildAt(int index, CanvasViewInfo child) {
+ private void addChildAt(int index, @NonNull CanvasViewInfo child) {
mChildren.add(index, child);
}
@@ -584,7 +604,7 @@ public class CanvasViewInfo implements IPropertySource {
* @param child the child to be removed
* @return true if it was a child and was removed
*/
- public boolean removeChild(CanvasViewInfo child) {
+ public boolean removeChild(@NonNull CanvasViewInfo child) {
return mChildren.remove(child);
}
@@ -623,6 +643,7 @@ public class CanvasViewInfo implements IPropertySource {
* @param root the root {@link ViewInfo} to build from
* @return a {@link CanvasViewInfo} hierarchy
*/
+ @NonNull
public static Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root, boolean layoutlib5) {
return new Builder(layoutlib5).create(root);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PropertySheetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PropertySheetPage.java
deleted file mode 100644
index d785faf..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PropertySheetPage.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.
- */
-
-package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
-
-import static com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor.DEPRECATED_CATEGORY;
-
-import org.eclipse.jface.action.IStatusLineManager;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.GC;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-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.swt.widgets.Tree;
-import org.eclipse.swt.widgets.TreeItem;
-import org.eclipse.ui.IActionBars;
-import org.eclipse.ui.views.properties.IPropertySheetEntry;
-import org.eclipse.ui.views.properties.PropertySheetEntry;
-import org.eclipse.ui.views.properties.PropertySheetSorter;
-
-/**
- * A customized property sheet page for the graphical layout editor v2.
- * <p/>
- * Currently it just provides a custom tooltip to display attributes javadocs.
- * <p/>
- * The property sheet is linked to the current site's selection service.
- * <p/>
- * Note: this is an exact copy of GLE1's UiPropertySheetPage implementation.
- * The idea is that eventually GLE1 will go away and we'll upgrade this to be
- * a more robust property editor (it currently lacks on so many levels, it's not
- * even worth listing the flaws.)
- *
- * @since GLE2
- */
-public class PropertySheetPage extends org.eclipse.ui.views.properties.PropertySheetPage {
- private static final String MISC_CATEGORY = "Misc";
-
- public PropertySheetPage() {
- super();
-
- setSorter(new PropertySheetSorter() {
- @Override
- public int compareCategories(String categoryA, String categoryB) {
- // Sort the "Deprecated" category to the bottom, and the "Misc"
- // category second to last.
- if (categoryA.equals(DEPRECATED_CATEGORY)) {
- return 1;
- } else if (categoryB.equals(DEPRECATED_CATEGORY)) {
- return -1;
- }
- if (categoryA.equals(MISC_CATEGORY)) {
- return 1;
- } else if (categoryB.equals(MISC_CATEGORY)) {
- return -1;
- }
-
- return super.compareCategories(categoryA, categoryB);
- }
- });
- }
-
- @Override
- public void createControl(Composite parent) {
- super.createControl(parent);
-
- setupTooltip();
-
- // Override parent class' "set status message" behavior. The parent will set
- // the status message to the property's "getDescription()" field. That field
- // may contain newlines, which means the text gets cut off. We want to instead
- // show ALL the text, fit on a single line, and since we don't get to subclass
- // the viewer we will just replace the status message with our own, which works
- // since our mouse listener is registered later so runs later.
- final Tree tree = (Tree) getControl();
- tree.addMouseListener(new MouseAdapter() {
- @Override
- public void mouseDown(MouseEvent event) {
- Point pt = new Point(event.x, event.y);
- TreeItem item = tree.getItem(pt);
- if (item != null) {
- Object object = item.getData();
- if (object instanceof IPropertySheetEntry) {
- IPropertySheetEntry entry = (IPropertySheetEntry) object;
- String help = entry.getDescription();
- if (help != null) {
- // Strip out newlines to make this a single line entry
- help = help.replace('\n', ' ');
- // Remove repeated spaces in case there were trailing spaces
- help = help.replaceAll(" ", " "); //$NON-NLS-1$ //$NON-NLS-2$
- IActionBars actionBars = getSite().getActionBars();
- IStatusLineManager status = actionBars.getStatusLineManager();
- status.setMessage(help);
- }
- }
- }
- }
- });
-
- // Fix the selection background. In Eclipse 3.5 and 3.6, the selection color
- // is white, painted on top of a white or light blue background (table striping),
- // which is practically unreadable. This is fixed in 3.7M3, but we need a workaround
- // for earlier releases. This just paints a solid color under the current line in
- // the left column.
- tree.addListener(SWT.EraseItem, new Listener() {
- @Override
- public void handleEvent(Event event) {
- if ((event.detail & SWT.SELECTED) != 0 && event.index == 0) {
- GC gc = event.gc;
- Rectangle rect = event.getBounds();
- Color background = gc.getBackground();
- Display display = tree.getDisplay();
- gc.setBackground(display.getSystemColor(SWT.COLOR_LIST_SELECTION));
- gc.fillRectangle(rect.x, rect.y, rect.width, rect.height);
- gc.setBackground(background);
- }
- }
- });
- }
-
- /**
- * Sets up a custom tooltip when hovering over tree items.
- * <p/>
- * The tooltip will display the element's javadoc, if any, or the item's getText otherwise.
- */
- private void setupTooltip() {
- final Tree tree = (Tree) getControl();
-
- /*
- * Reference:
- * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup
- */
-
- final Listener listener = new Listener() {
- Shell tip = null;
- Label label = null;
-
- @Override
- public void handleEvent(Event event) {
- switch(event.type) {
- case SWT.Dispose:
- case SWT.KeyDown:
- case SWT.MouseExit:
- case SWT.MouseDown:
- case SWT.MouseMove:
- if (tip != null) {
- tip.dispose();
- tip = null;
- label = null;
- }
- break;
- case SWT.MouseHover:
- if (tip != null) {
- tip.dispose();
- tip = null;
- label = null;
- }
-
- String tooltip = null;
-
- TreeItem item = tree.getItem(new Point(event.x, event.y));
- if (item != null) {
- Object data = item.getData();
- if (data instanceof PropertySheetEntry) {
- tooltip = ((PropertySheetEntry) data).getDescription();
- }
-
- if (tooltip == null) {
- tooltip = item.getText();
- } else {
- tooltip = item.getText() + ":\r" + tooltip;
- }
-
- if (tooltip != null) {
- Shell shell = tree.getShell();
- Display display = tree.getDisplay();
-
- tip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
- tip.setBackground(display .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
- FillLayout layout = new FillLayout();
- layout.marginWidth = 2;
- tip.setLayout(layout);
- label = new Label(tip, SWT.NONE);
- label.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
- label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
- label.setData("_TABLEITEM", item);
- label.setText(tooltip);
- label.addListener(SWT.MouseExit, this);
- label.addListener(SWT.MouseDown, this);
- Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
- Rectangle rect = item.getBounds(0);
- // Display the tooltip on the same line as the property,
- // but offset to the right of wherever the mouse cursor was,
- // such that it does not obscure the list of properties.
- Point pt = tree.toDisplay(event.x + 15, rect.y);
- tip.setBounds(pt.x, pt.y, size.x, size.y);
- tip.setVisible(true);
- }
- }
- }
- }
- };
-
- tree.addListener(SWT.Dispose, listener);
- tree.addListener(SWT.KeyDown, listener);
- tree.addListener(SWT.MouseMove, listener);
- tree.addListener(SWT.MouseHover, listener);
-
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java
index 7c2f7f5..5d49426 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java
@@ -16,6 +16,8 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.api.ResizePolicy;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
@@ -97,11 +99,22 @@ class SelectionItem {
*
* @return the selected view info. Cannot be null.
*/
+ @NonNull
public CanvasViewInfo getViewInfo() {
return mCanvasViewInfo;
}
/**
+ * Returns the selected node.
+ *
+ * @return the selected node, or null
+ */
+ @Nullable
+ public UiViewElementNode getUiNode() {
+ return mCanvasViewInfo.getUiViewNode();
+ }
+
+ /**
* Returns the selection border rectangle. Cannot be null.
*
* @return the selection border rectangle, never null
@@ -111,11 +124,13 @@ class SelectionItem {
}
/** Returns the node associated with this selection (may be null) */
+ @Nullable
NodeProxy getNode() {
return mNodeProxy;
}
/** Returns the canvas associated with this selection (never null) */
+ @NonNull
LayoutCanvas getCanvas() {
return mCanvas;
}
@@ -126,6 +141,7 @@ class SelectionItem {
* Gets the XML text from the given selection for a text transfer.
* The returned string can be empty but not null.
*/
+ @NonNull
static String getAsText(LayoutCanvas canvas, List<SelectionItem> selection) {
StringBuilder sb = new StringBuilder();
@@ -152,6 +168,7 @@ class SelectionItem {
* @param items Items to wrap in elements
* @return An array of wrapper elements. Never null.
*/
+ @NonNull
static SimpleElement[] getAsElements(List<SelectionItem> items) {
ArrayList<SimpleElement> elements = new ArrayList<SimpleElement>();
@@ -184,6 +201,7 @@ class SelectionItem {
*
* @return the {@link SelectionHandles} for this {@link SelectionItem}, never null
*/
+ @NonNull
public SelectionHandles getSelectionHandles() {
if (mHandles == null) {
mHandles = new SelectionHandles(this);
@@ -197,6 +215,7 @@ class SelectionItem {
*
* @return the {@link ResizePolicy} for this item, never null
*/
+ @NonNull
public ResizePolicy getResizePolicy() {
if (mResizePolicy == null && mNodeProxy != null) {
mResizePolicy = ViewMetadataRepository.get().getResizePolicy(mNodeProxy.getFqcn());
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
index e2c573a..1450768 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
@@ -20,6 +20,7 @@ import static com.android.ide.common.layout.LayoutConstants.FQCN_SPACE_V7;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_MARGIN;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_RADIUS;
+import com.android.annotations.NonNull;
import com.android.ide.common.api.INode;
import com.android.ide.common.layout.GridLayoutRule;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
@@ -122,6 +123,7 @@ public class SelectionManager implements ISelectionProvider {
*
* @return An immutable list of {@link SelectionItem}. Can be empty but not null.
*/
+ @NonNull
List<SelectionItem> getSelections() {
return mUnmodifiableSelection;
}
@@ -132,7 +134,12 @@ public class SelectionManager implements ISelectionProvider {
*
* @return A copy of the current selection. Never null.
*/
- /* package */ List<SelectionItem> getSnapshot() {
+ @NonNull
+ public List<SelectionItem> getSnapshot() {
+ if (mSelectionListeners.isEmpty()) {
+ return Collections.emptyList();
+ }
+
return new ArrayList<SelectionItem>(mSelections);
}
@@ -190,6 +197,7 @@ public class SelectionManager implements ISelectionProvider {
return;
}
+ boolean changed = false;
try {
mInsideUpdateSelection = true;
@@ -211,7 +219,6 @@ public class SelectionManager implements ISelectionProvider {
return;
}
- boolean changed = false;
boolean redoLayout = false;
// Create a list of all currently selected view infos
@@ -242,6 +249,10 @@ public class SelectionManager implements ISelectionProvider {
if (newVi.isInvisible()) {
redoLayout = true;
}
+ } else {
+ // Unrelated selection (e.g. user clicked in the Project Explorer
+ // or something) -- just ignore these
+ return;
}
}
@@ -257,15 +268,16 @@ public class SelectionManager implements ISelectionProvider {
if (redoLayout) {
mCanvas.getEditorDelegate().recomputeLayout();
}
- if (changed) {
- redraw();
- updateActionsFromSelection();
- }
-
}
} finally {
mInsideUpdateSelection = false;
}
+
+ if (changed) {
+ redraw();
+ fireSelectionChanged();
+ updateActionsFromSelection();
+ }
}
/**
@@ -699,7 +711,7 @@ public class SelectionManager implements ISelectionProvider {
}
/** Sync the selection with an updated view info tree */
- /* package */ void sync() {
+ void sync() {
// Check if the selection is still the same (based on the object keys)
// and eventually recompute their bounds.
for (ListIterator<SelectionItem> it = mSelections.listIterator(); it.hasNext(); ) {
@@ -714,6 +726,9 @@ public class SelectionManager implements ISelectionProvider {
// we need to recompute its bounds in case it moved so we'll insert a new one
// at the same place.
it.remove();
+ if (vi == null) {
+ vi = findCorresponding(s.getViewInfo(), viewHierarchy.getRoot());
+ }
if (vi != null) {
it.add(createSelection(vi));
}
@@ -724,6 +739,39 @@ public class SelectionManager implements ISelectionProvider {
mAltSelection = null;
}
+ /** Finds the corresponding {@link CanvasViewInfo} in the new hierarchy */
+ private CanvasViewInfo findCorresponding(CanvasViewInfo old, CanvasViewInfo newRoot) {
+ CanvasViewInfo oldParent = old.getParent();
+ if (oldParent != null) {
+ CanvasViewInfo newParent = findCorresponding(oldParent, newRoot);
+ if (newParent == null) {
+ return null;
+ }
+
+ List<CanvasViewInfo> oldSiblings = oldParent.getChildren();
+ List<CanvasViewInfo> newSiblings = newParent.getChildren();
+ Iterator<CanvasViewInfo> oldIterator = oldSiblings.iterator();
+ Iterator<CanvasViewInfo> newIterator = newSiblings.iterator();
+ while (oldIterator.hasNext() && newIterator.hasNext()) {
+ CanvasViewInfo oldSibling = oldIterator.next();
+ CanvasViewInfo newSibling = newIterator.next();
+
+ if (oldSibling.getName().equals(newSibling.getName())) {
+ // Structure has changed: can't do a proper search
+ return null;
+ }
+
+ if (oldSibling == old) {
+ return newSibling;
+ }
+ }
+ } else {
+ return newRoot;
+ }
+
+ return null;
+ }
+
/**
* Notifies listeners that the selection has changed.
*/
@@ -884,9 +932,13 @@ public class SelectionManager implements ISelectionProvider {
newChildren.add(viewInfo);
}
}
- mCanvas.getSelectionManager().selectMultiple(newChildren);
+ boolean found = nodes.size() == newChildren.size();
+
+ if (found || newChildren.size() > 0) {
+ mCanvas.getSelectionManager().selectMultiple(newChildren);
+ }
- return nodes.size() == newChildren.size();
+ return found;
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java
index d0e957e..99d6505 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.common.api.Rect;
import org.eclipse.swt.SWTException;
+import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
@@ -123,7 +124,7 @@ public class SwtUtils {
* @return A new SWT {@link Image} with the same contents as the source
* {@link BufferedImage}
*/
- public static Image convertToSwt(Display display, BufferedImage awtImage,
+ public static Image convertToSwt(Device display, BufferedImage awtImage,
boolean transferAlpha, int globalAlpha) {
if (!isSupportedPaletteType(awtImage.getType())) {
awtImage = convertToCompatibleFormat(awtImage);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
index f6437fc..579ef44 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
@@ -18,6 +18,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.api.INode;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.ViewInfo;
@@ -179,7 +181,7 @@ public class ViewHierarchy {
if (root != null) {
infos = CanvasViewInfo.create(root, layoutlib5);
if (DUMP_INFO) {
- dump(root, 0);
+ dump(session, root, 0);
}
} else {
infos = null;
@@ -693,12 +695,27 @@ public class ViewHierarchy {
}
/**
+ * Returns a map of the default properties for the given view object in this session
+ *
+ * @param viewObject the object to look up the properties map for
+ * @return the map of properties, or null if not found
+ */
+ @Nullable
+ public Map<String, String> getDefaultProperties(@NonNull Object viewObject) {
+ if (mSession != null) {
+ return mSession.getDefaultProperties(viewObject);
+ }
+
+ return null;
+ }
+
+ /**
* Dumps a {@link ViewInfo} hierarchy to stdout
*
* @param info the {@link ViewInfo} object to dump
* @param depth the depth to indent it to
*/
- public static void dump(ViewInfo info, int depth) {
+ public static void dump(RenderSession session, ViewInfo info, int depth) {
if (DUMP_INFO) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
@@ -724,11 +741,19 @@ public class ViewHierarchy {
} else if (cookie != null) {
sb.append(" " + cookie); //$NON-NLS-1$
}
+ /* Display defaults?
+ if (info.getViewObject() != null) {
+ Map<String, String> defaults = session.getDefaultProperties(info.getCookie());
+ sb.append(" - defaults: "); //$NON-NLS-1$
+ sb.append(defaults);
+ sb.append('\n');
+ }
+ */
System.out.println(sb.toString());
for (ViewInfo child : info.getChildren()) {
- dump(child, depth + 1);
+ dump(session, child, depth + 1);
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/PaletteMetadataDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/PaletteMetadataDescriptor.java
index 3380a38..d6d5cd9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/PaletteMetadataDescriptor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/PaletteMetadataDescriptor.java
@@ -47,6 +47,7 @@ public class PaletteMetadataDescriptor extends ViewElementDescriptor {
descriptor.getChildren(), descriptor.getMandatory() == Mandatory.MANDATORY);
mInitString = initString;
mIconName = iconName;
+ setSuperClass(descriptor.getSuperClassDesc());
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/BooleanXmlPropertyEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/BooleanXmlPropertyEditor.java
new file mode 100644
index 0000000..32b1192
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/BooleanXmlPropertyEditor.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import static com.android.ide.common.layout.LayoutConstants.VALUE_FALSE;
+import static com.android.ide.common.layout.LayoutConstants.VALUE_TRUE;
+
+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;
+
+/**
+ * Handle an XML property which represents booleans.
+ *
+ * Similar to the WindowBuilder PropertyEditor, but operates on Strings rather
+ * than Booleans (which means it is a tri-state boolean: true, false, not set)
+ */
+public class BooleanXmlPropertyEditor extends XmlPropertyEditor {
+ public static final BooleanXmlPropertyEditor INSTANCE = new BooleanXmlPropertyEditor();
+
+ private static final Image mTrueImage = DesignerPlugin.getImage("properties/true.png");
+ private static final Image mFalseImage = DesignerPlugin.getImage("properties/false.png");
+ private static final Image mNullImage =
+ DesignerPlugin.getImage("properties/BooleanNull.png");
+ private static final Image mUnknownImage =
+ DesignerPlugin.getImage("properties/BooleanUnknown.png");
+
+ private BooleanXmlPropertyEditor() {
+ }
+
+ @Override
+ public void paint(Property property, GC gc, int x, int y, int width, int height)
+ throws Exception {
+ Object value = property.getValue();
+ assert value == null || value instanceof String;
+ if (value == null || value instanceof String) {
+ String text = (String) value;
+ Image image;
+ if (VALUE_TRUE.equals(text)) {
+ image = mTrueImage;
+ } else if (VALUE_FALSE.equals(text)) {
+ image = mFalseImage;
+ } else if (text == null) {
+ image = mNullImage;
+ } else {
+ // Probably something like a reference, e.g. @boolean/foo
+ image = mUnknownImage;
+ }
+
+ // draw image
+ DrawUtils.drawImageCV(gc, image, x, y, height);
+
+ // prepare new position/width
+ int imageWidth = image.getBounds().width + 2;
+ width -= imageWidth;
+
+ // draw text
+ if (text != null) {
+ x += imageWidth;
+ DrawUtils.drawStringCV(gc, text, x, y, width, height);
+ }
+ }
+ }
+
+ @Override
+ public boolean activate(PropertyTable propertyTable, Property property, Point location)
+ throws Exception {
+ // check that user clicked on image
+ if (location == null || location.x < mTrueImage.getBounds().width + 2) {
+ cycleValue(property);
+ }
+ // don't activate
+ return false;
+ }
+
+ @Override
+ public void doubleClick(Property property, Point location) throws Exception {
+ cycleValue(property);
+ }
+
+ /**
+ * Cycles through the values
+ */
+ private void cycleValue(Property property) throws Exception {
+ Object value = property.getValue();
+ if (value == null || value instanceof String) {
+ // Cycle null => true => false => null
+ String text = (String) value;
+ if (VALUE_TRUE.equals(text)) {
+ property.setValue(VALUE_FALSE);
+ } else if (VALUE_FALSE.equals(text)) {
+ property.setValue(null);
+ } else {
+ property.setValue(VALUE_TRUE);
+ }
+ } else {
+ assert false;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/EnumXmlPropertyEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/EnumXmlPropertyEditor.java
new file mode 100644
index 0000000..f1a3f2a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/EnumXmlPropertyEditor.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ListAttributeDescriptor;
+
+import org.eclipse.wb.core.controls.CCombo3;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.editor.AbstractComboPropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.ITextValuePropertyEditor;
+
+class EnumXmlPropertyEditor extends AbstractComboPropertyEditor implements
+ ITextValuePropertyEditor {
+ public static final EnumXmlPropertyEditor INSTANCE = new EnumXmlPropertyEditor();
+
+ private EnumXmlPropertyEditor() {
+ }
+
+ @Override
+ protected String getText(Property property) throws Exception {
+ Object value = property.getValue();
+ if (value == null) {
+ return "";
+ } else if (value instanceof String) {
+ return (String) value;
+ } else if (value == Property.UNKNOWN_VALUE) {
+ return "<varies>";
+ } else {
+ return "";
+ }
+ }
+
+ private String[] getItems(Property property) {
+ XmlProperty xmlProperty = (XmlProperty) property;
+ AttributeDescriptor descriptor = xmlProperty.getDescriptor();
+ assert descriptor instanceof ListAttributeDescriptor;
+ ListAttributeDescriptor list = (ListAttributeDescriptor) descriptor;
+ return list.getValues();
+ }
+
+ @Override
+ protected void addItems(Property property, CCombo3 combo) throws Exception {
+ for (String item : getItems(property)) {
+ 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(getItems(property)[index]);
+ }
+
+ @Override
+ public void setText(Property property, String text) throws Exception {
+ property.setValue(text);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagValueCompleter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagValueCompleter.java
new file mode 100644
index 0000000..d3707a5
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagValueCompleter.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import com.android.ide.eclipse.adt.AdtUtils;
+
+import org.eclipse.jface.fieldassist.ContentProposal;
+import org.eclipse.jface.fieldassist.IContentProposal;
+import org.eclipse.jface.fieldassist.IContentProposalProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Resource value completion for the given property */
+class FlagValueCompleter implements IContentProposalProvider {
+ protected final XmlProperty mProperty;
+ private String[] mValues;
+
+ FlagValueCompleter(XmlProperty property, String[] values) {
+ mProperty = property;
+ mValues = values;
+ }
+
+ @Override
+ public IContentProposal[] getProposals(String contents, int position) {
+ List<IContentProposal> proposals = new ArrayList<IContentProposal>(mValues.length);
+ String prefix = contents;
+ int flagStart = prefix.lastIndexOf('|');
+ String prepend = null;
+ if (flagStart != -1) {
+ prepend = prefix.substring(0, flagStart + 1);
+ prefix = prefix.substring(flagStart + 1).trim();
+ }
+
+ boolean exactMatch = false;
+ for (String value : mValues) {
+ if (prefix.equals(value)) {
+ exactMatch = true;
+ proposals.add(new ContentProposal(contents));
+
+ break;
+ }
+ }
+
+ if (exactMatch) {
+ prepend = contents + '|';
+ prefix = "";
+ }
+
+ for (String value : mValues) {
+ if (AdtUtils.startsWithIgnoreCase(value, prefix)) {
+ if (prepend != null && prepend.contains(value)) {
+ continue;
+ }
+ String match;
+ if (prepend != null) {
+ match = prepend + value;
+ } else {
+ match = value;
+ }
+ proposals.add(new ContentProposal(match));
+ }
+ }
+
+ return proposals.toArray(new IContentProposal[proposals.size()]);
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagXmlPropertyDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagXmlPropertyDialog.java
new file mode 100644
index 0000000..0276b6c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagXmlPropertyDialog.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import com.android.annotations.NonNull;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.google.common.base.Splitter;
+
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+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.dialogs.ResizableDialog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class FlagXmlPropertyDialog extends ResizableDialog
+implements IStructuredContentProvider, ICheckStateListener, SelectionListener, KeyListener {
+ private final String mTitle;
+ private final XmlProperty mProperty;
+ private final String[] mFlags;
+ private final boolean mIsRadio;
+
+ private Table mTable;
+ private CheckboxTableViewer mViewer;
+
+ FlagXmlPropertyDialog(
+ @NonNull Shell parentShell,
+ @NonNull String title,
+ boolean isRadio,
+ @NonNull String[] flags,
+ @NonNull XmlProperty property) {
+ super(parentShell, AdtPlugin.getDefault());
+ mTitle = title;
+ mIsRadio = isRadio;
+ mFlags = flags;
+ mProperty = property;
+ }
+
+ @Override
+ protected void configureShell(Shell newShell) {
+ super.configureShell(newShell);
+ newShell.setText(mTitle);
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite container = (Composite) super.createDialogArea(parent);
+
+ mViewer = CheckboxTableViewer.newCheckList(container,
+ SWT.BORDER | SWT.FULL_SELECTION | SWT.HIDE_SELECTION);
+ mTable = mViewer.getTable();
+ mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+
+ mViewer.setContentProvider(this);
+ mViewer.setInput(mFlags);
+
+ String current = mProperty.getStringValue();
+ if (current != null) {
+ Object[] checked = null;
+ if (mIsRadio) {
+ checked = new String[] { current };
+ } else {
+ List<String> flags = new ArrayList<String>();
+ for (String s : Splitter.on('|').omitEmptyStrings().trimResults().split(current)) {
+ flags.add(s);
+ }
+ checked = flags.toArray(new String[flags.size()]);
+ }
+ mViewer.setCheckedElements(checked);
+ }
+ if (mFlags.length > 0) {
+ mTable.setSelection(0);
+ }
+
+ if (mIsRadio) {
+ // Enforce single-item selection
+ mViewer.addCheckStateListener(this);
+ }
+ mTable.addSelectionListener(this);
+ mTable.addKeyListener(this);
+
+ return container;
+ }
+
+ @Override
+ protected void createButtonsForButtonBar(Composite parent) {
+ createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
+ createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
+ }
+
+ @Override
+ protected Point getDefaultSize() {
+ return new Point(450, 400);
+ }
+
+ @Override
+ protected void okPressed() {
+ // Apply the value
+ ExecutionUtils.runLog(new RunnableEx() {
+ @Override
+ public void run() throws Exception {
+ StringBuilder sb = new StringBuilder(30);
+ for (Object o : mViewer.getCheckedElements()) {
+ if (sb.length() > 0) {
+ sb.append('|');
+ }
+ sb.append((String) o);
+ }
+ String value = sb.length() > 0 ? sb.toString() : null;
+ mProperty.setValue(value);
+ }
+ });
+
+ // close dialog
+ super.okPressed();
+ }
+
+ // ---- Implements IStructuredContentProvider ----
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ return (Object []) inputElement;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ // ---- Implements ICheckStateListener ----
+
+ @Override
+ public void checkStateChanged(CheckStateChangedEvent event) {
+ // Try to disable other elements that conflict with this
+ boolean isChecked = event.getChecked();
+ if (isChecked) {
+ Object selected = event.getElement();
+ for (Object other : mViewer.getCheckedElements()) {
+ if (other != selected) {
+ mViewer.setChecked(other, false);
+ }
+ }
+ } else {
+
+ }
+ }
+
+ // ---- Implements SelectionListener ----
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ if (e.item instanceof TableItem) {
+ TableItem item = (TableItem) e.item;
+ item.setChecked(!item.getChecked());
+ }
+ }
+
+ // ---- Implements KeyListener ----
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ // Let space toggle checked state
+ if (e.keyCode == ' ' /* SWT.SPACE requires Eclipse 3.7 */) {
+ if (mTable.getSelectionCount() == 1) {
+ TableItem item = mTable.getSelection()[0];
+ item.setChecked(!item.getChecked());
+ }
+ }
+ }
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java
new file mode 100644
index 0000000..1ce7197
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java
@@ -0,0 +1,688 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX;
+
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.common.api.IAttributeInfo.Format;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import org.eclipse.wb.internal.core.editor.structure.property.PropertyListIntersector;
+import org.eclipse.wb.internal.core.model.property.ComplexProperty;
+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.editor.PropertyEditor;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The {@link PropertyFactory} creates (and caches) the set of {@link Property}
+ * instances applicable to a given node. It's also responsible for ordering
+ * these, and sometimes combining them into {@link ComplexProperty} category
+ * nodes.
+ * <p>
+ * TODO: For any properties that are *set* in XML, they should NOT be labeled as
+ * advanced (which would make them disappear)
+ */
+class PropertyFactory {
+ /** Disable cache during development only */
+ private static final boolean CACHE_ENABLED = true || !LintUtils.assertionsEnabled();
+ static {
+ if (!CACHE_ENABLED) {
+ System.err.println("WARNING: The property cache is disabled");
+ }
+ }
+
+ private static final Property[] NO_PROPERTIES = new Property[0];
+
+ private static final int PRIO_FIRST = -100000;
+ private static final int PRIO_SECOND = PRIO_FIRST + 10;
+ private static final int PRIO_LAST = 100000;
+
+ private final GraphicalEditorPart mGraphicalEditorPart;
+ private final PropertyTable mPropertyTable;
+ private Map<UiViewElementNode, Property[]> mCache = Maps.newHashMap();
+ private UiViewElementNode mCurrentViewCookie;
+
+ /** Sorting orders for the properties */
+ public enum SortingMode {
+ NATURAL,
+ BY_ORIGIN,
+ ALPHABETICAL;
+ }
+
+ /** The default sorting mode */
+ public static final SortingMode DEFAULT_MODE = SortingMode.BY_ORIGIN;
+
+ private SortingMode mSortMode = DEFAULT_MODE;
+ private SortingMode mCacheSortMode;
+
+ PropertyFactory(GraphicalEditorPart graphicalEditorPart, PropertyTable propertyTable) {
+ mGraphicalEditorPart = graphicalEditorPart;
+ mPropertyTable = propertyTable;
+ }
+
+ /**
+ * Get the properties for the given list of selection items.
+ *
+ * @param items the {@link CanvasViewInfo} instances to get an intersected
+ * property list for
+ * @return the properties for the given items
+ */
+ public Property[] getProperties(List<CanvasViewInfo> items) {
+ mCurrentViewCookie = null;
+
+ if (items == null || items.size() == 0) {
+ return NO_PROPERTIES;
+ } else if (items.size() == 1) {
+ CanvasViewInfo item = items.get(0);
+ mCurrentViewCookie = item.getUiViewNode();
+
+ return getProperties(item);
+ } else {
+ // intersect properties
+ PropertyListIntersector intersector = new PropertyListIntersector();
+ for (CanvasViewInfo node : items) {
+ intersector.intersect(getProperties(node));
+ }
+
+ return intersector.getProperties();
+ }
+ }
+
+ private Property[] getProperties(CanvasViewInfo item) {
+ UiViewElementNode node = item.getUiViewNode();
+ if (node == null) {
+ return NO_PROPERTIES;
+ }
+
+ if (mCacheSortMode != mSortMode) {
+ mCacheSortMode = mSortMode;
+ mCache.clear();
+ }
+
+ Property[] properties = mCache.get(node);
+ if (!CACHE_ENABLED) {
+ properties = null;
+ }
+ if (properties == null) {
+ Collection<? extends Property> propertyList = getProperties(node, mPropertyTable);
+ if (propertyList == null) {
+ properties = new Property[0];
+ } else {
+ properties = propertyList.toArray(new Property[propertyList.size()]);
+ }
+ mCache.put(node, properties);
+ }
+ return properties;
+ }
+
+
+ protected Collection<? extends Property> getProperties(
+ UiViewElementNode node,
+ PropertyTable propertyTable) {
+ ViewMetadataRepository repository = ViewMetadataRepository.get();
+ ViewElementDescriptor viewDescriptor = (ViewElementDescriptor) node.getDescriptor();
+ String fqcn = viewDescriptor.getFullClassName();
+ Set<String> top = new HashSet<String>(repository.getTopAttributes(fqcn));
+ AttributeDescriptor[] attributeDescriptors = node.getAttributeDescriptors();
+
+ List<XmlProperty> properties = new ArrayList<XmlProperty>(attributeDescriptors.length);
+ int priority = 0;
+ for (final AttributeDescriptor descriptor : attributeDescriptors) {
+ // TODO: Filter out non-public properties!!
+ // (They shouldn't be in the descriptors at all)
+
+ assert !(descriptor instanceof SeparatorAttributeDescriptor); // No longer inserted
+ if (descriptor instanceof XmlnsAttributeDescriptor) {
+ continue;
+ }
+
+ PropertyEditor editor = XmlPropertyEditor.INSTANCE;
+ IAttributeInfo info = descriptor.getAttributeInfo();
+ if (info != null) {
+ EnumSet<Format> formats = info.getFormats();
+ if (formats.contains(Format.BOOLEAN)) {
+ editor = BooleanXmlPropertyEditor.INSTANCE;
+ } else if (formats.contains(Format.ENUM)) {
+ editor = EnumXmlPropertyEditor.INSTANCE;
+ }
+ }
+
+ XmlProperty property = new XmlProperty(editor, this, node, descriptor);
+ // Assign ids sequentially. This ensures that the properties will mostly keep their
+ // relative order (such as placing width before height), even though we will regroup
+ // some (such as properties in the same category, and the layout params etc)
+ priority += 10;
+
+ PropertyCategory category = PropertyCategory.NORMAL;
+ String name = descriptor.getXmlLocalName();
+ if (top.contains(name) || PropertyMetadata.isPreferred(name)) {
+ category = PropertyCategory.PREFERRED;
+ property.setPriority(PRIO_FIRST + priority);
+ } else {
+ property.setPriority(priority);
+
+ // Prefer attributes defined on the specific type of this
+ // widget
+ // NOTE: This doesn't work very well for TextViews
+ /* IAttributeInfo attributeInfo = descriptor.getAttributeInfo();
+ if (attributeInfo != null && fqcn.equals(attributeInfo.getDefinedBy())) {
+ category = PropertyCategory.PREFERRED;
+ } else*/ if (PropertyMetadata.isAdvanced(name)) {
+ category = PropertyCategory.ADVANCED;
+ }
+ }
+ if (category != null) {
+ property.setCategory(category);
+ }
+ properties.add(property);
+ }
+
+ switch (mSortMode) {
+ case BY_ORIGIN:
+ return sortByOrigin(node, properties);
+
+ case ALPHABETICAL:
+ return sortAlphabetically(node, properties);
+
+ default:
+ case NATURAL:
+ return sortNatural(node, properties);
+ }
+ }
+
+ protected Collection<? extends Property> sortAlphabetically(
+ UiViewElementNode node,
+ List<XmlProperty> properties) {
+ Collections.sort(properties, Property.ALPHABETICAL);
+ return properties;
+ }
+
+ protected Collection<? extends Property> sortByOrigin(
+ UiViewElementNode node,
+ List<XmlProperty> properties) {
+ List<Property> collapsed = new ArrayList<Property>(properties.size());
+ List<Property> layoutProperties = Lists.newArrayListWithExpectedSize(20);
+ List<Property> marginProperties = null;
+ List<Property> deprecatedProperties = null;
+ Map<String, ComplexProperty> categoryToProperty = new HashMap<String, ComplexProperty>();
+ Multimap<String, Property> categoryToProperties = ArrayListMultimap.create();
+
+
+ ViewElementDescriptor parent = (ViewElementDescriptor) properties.get(0).getDescriptor()
+ .getParent();
+ Map<String, Integer> categoryPriorities = Maps.newHashMap();
+ int nextCategoryPriority = 100;
+ while (parent != null) {
+ categoryPriorities.put(parent.getFullClassName(), nextCategoryPriority += 100);
+ parent = parent.getSuperClassDesc();
+ }
+
+ for (int i = 0, max = properties.size(); i < max; i++) {
+ XmlProperty property = properties.get(i);
+
+ AttributeDescriptor descriptor = property.getDescriptor();
+ if (descriptor.isDeprecated()) {
+ if (deprecatedProperties == null) {
+ deprecatedProperties = Lists.newArrayListWithExpectedSize(10);
+ }
+ deprecatedProperties.add(property);
+ continue;
+ }
+
+ String firstName = descriptor.getXmlLocalName();
+ if (firstName.startsWith(ATTR_LAYOUT_PREFIX)) {
+ if (firstName.startsWith(ATTR_LAYOUT_MARGIN)) {
+ if (marginProperties == null) {
+ marginProperties = Lists.newArrayListWithExpectedSize(5);
+ }
+ marginProperties.add(property);
+ } else {
+ layoutProperties.add(property);
+ }
+ continue;
+ }
+
+ if (firstName.equals(ATTR_ID)) {
+ // Add id to the front (though the layout parameters will be added to
+ // the front of this at the end)
+ property.setPriority(PRIO_FIRST);
+ collapsed.add(property);
+ continue;
+ }
+
+ if (property.getCategory() == PropertyCategory.PREFERRED) {
+ collapsed.add(property);
+ // Fall through: these are *duplicated* inside their defining categories!
+ // However, create a new instance of the property, such that the propertysheet
+ // doesn't see the same property instance twice (when selected, it will highlight
+ // both, etc.) Also, set the category to Normal such that we don't draw attention
+ // to it again. We want it to appear in both places such that somebody looking
+ // within a category will always find it there, even if for this specific
+ // view type it's a common attribute and replicated up at the top.
+ XmlProperty oldProperty = property;
+ property = new XmlProperty(oldProperty.getEditor(), this, node,
+ oldProperty.getDescriptor());
+ property.setPriority(oldProperty.getPriority());
+ }
+
+ IAttributeInfo attributeInfo = descriptor.getAttributeInfo();
+ if (attributeInfo != null && attributeInfo.getDefinedBy() != null) {
+ String category = attributeInfo.getDefinedBy();
+ ComplexProperty complex = categoryToProperty.get(category);
+ if (complex == null) {
+ complex = new ComplexProperty(
+ category.substring(category.lastIndexOf('.') + 1),
+ "[]",
+ null /* properties */);
+ categoryToProperty.put(category, complex);
+ Integer categoryPriority = categoryPriorities.get(category);
+ if (categoryPriority != null) {
+ complex.setPriority(categoryPriority);
+ } else {
+ // Descriptor for an attribute whose definedBy does *not*
+ // correspond to one of the known superclasses of this widget.
+ // This sometimes happens; for example, a RatingBar will pull in
+ // an ImageView's minWidth attribute. Probably an error in the
+ // metadata, but deal with it gracefully here.
+ categoryPriorities.put(category, nextCategoryPriority += 100);
+ complex.setPriority(nextCategoryPriority);
+ }
+ }
+ categoryToProperties.put(category, property);
+ continue;
+ } else {
+ collapsed.add(property);
+ }
+ }
+
+ // Update the complex properties
+ for (String category : categoryToProperties.keySet()) {
+ Collection<Property> subProperties = categoryToProperties.get(category);
+ if (subProperties.size() > 1) {
+ ComplexProperty complex = categoryToProperty.get(category);
+ assert complex != null : category;
+ Property[] subArray = new Property[subProperties.size()];
+ complex.setProperties(subProperties.toArray(subArray));
+ //complex.setPriority(subArray[0].getPriority());
+
+ collapsed.add(complex);
+
+ boolean allAdvanced = true;
+ boolean isPreferred = false;
+ for (Property p : subProperties) {
+ PropertyCategory c = p.getCategory();
+ if (c != PropertyCategory.ADVANCED) {
+ allAdvanced = false;
+ }
+ if (c == PropertyCategory.PREFERRED) {
+ isPreferred = true;
+ }
+ }
+ if (isPreferred) {
+ complex.setCategory(PropertyCategory.PREFERRED);
+ } else if (allAdvanced) {
+ complex.setCategory(PropertyCategory.ADVANCED);
+ }
+ } else if (subProperties.size() == 1) {
+ collapsed.add(subProperties.iterator().next());
+ }
+ }
+
+ if (layoutProperties.size() > 0 || marginProperties != null) {
+ if (marginProperties != null) {
+ XmlProperty[] m =
+ marginProperties.toArray(new XmlProperty[marginProperties.size()]);
+ Property marginProperty = new ComplexProperty(
+ "Margins",
+ "[]",
+ m);
+ layoutProperties.add(marginProperty);
+ marginProperty.setPriority(PRIO_LAST);
+
+ for (XmlProperty p : m) {
+ p.setParent(marginProperty);
+ }
+ }
+ Property[] l = layoutProperties.toArray(new Property[layoutProperties.size()]);
+ Arrays.sort(l, Property.PRIORITY);
+ Property property = new ComplexProperty(
+ "Layout Parameters",
+ "[]",
+ l);
+ for (Property p : l) {
+ if (p instanceof XmlProperty) {
+ ((XmlProperty) p).setParent(property);
+ }
+ }
+ property.setCategory(PropertyCategory.PREFERRED);
+ collapsed.add(property);
+ property.setPriority(PRIO_SECOND);
+ }
+
+ if (deprecatedProperties != null && deprecatedProperties.size() > 0) {
+ Property property = new ComplexProperty(
+ "Deprecated",
+ "(Deprecated Properties)",
+ deprecatedProperties.toArray(new Property[deprecatedProperties.size()]));
+ property.setPriority(PRIO_LAST);
+ collapsed.add(property);
+ }
+
+ Collections.sort(collapsed, Property.PRIORITY);
+
+ return collapsed;
+ }
+
+ protected Collection<? extends Property> sortNatural(
+ UiViewElementNode node,
+ List<XmlProperty> properties) {
+ Collections.sort(properties, Property.ALPHABETICAL);
+ List<Property> collapsed = new ArrayList<Property>(properties.size());
+ List<Property> layoutProperties = Lists.newArrayListWithExpectedSize(20);
+ List<Property> marginProperties = null;
+ List<Property> deprecatedProperties = null;
+ Map<String, ComplexProperty> categoryToProperty = new HashMap<String, ComplexProperty>();
+ Multimap<String, Property> categoryToProperties = ArrayListMultimap.create();
+
+ for (int i = 0, max = properties.size(); i < max; i++) {
+ XmlProperty property = properties.get(i);
+
+ AttributeDescriptor descriptor = property.getDescriptor();
+ if (descriptor.isDeprecated()) {
+ if (deprecatedProperties == null) {
+ deprecatedProperties = Lists.newArrayListWithExpectedSize(10);
+ }
+ deprecatedProperties.add(property);
+ continue;
+ }
+
+ String firstName = descriptor.getXmlLocalName();
+ if (firstName.startsWith(ATTR_LAYOUT_PREFIX)) {
+ if (firstName.startsWith(ATTR_LAYOUT_MARGIN)) {
+ if (marginProperties == null) {
+ marginProperties = Lists.newArrayListWithExpectedSize(5);
+ }
+ marginProperties.add(property);
+ } else {
+ layoutProperties.add(property);
+ }
+ continue;
+ }
+
+ if (firstName.equals(ATTR_ID)) {
+ // Add id to the front (though the layout parameters will be added to
+ // the front of this at the end)
+ property.setPriority(PRIO_FIRST);
+ collapsed.add(property);
+ continue;
+ }
+
+ String category = PropertyMetadata.getCategory(firstName);
+ if (category != null) {
+ ComplexProperty complex = categoryToProperty.get(category);
+ if (complex == null) {
+ complex = new ComplexProperty(
+ category,
+ "[]",
+ null /* properties */);
+ categoryToProperty.put(category, complex);
+ complex.setPriority(property.getPriority());
+ }
+ categoryToProperties.put(category, property);
+ continue;
+ }
+
+ // Index of second word in the first name, so in fooBar it's 3 (index of 'B')
+ int firstNameIndex = firstName.length();
+ for (int k = 0, kn = firstName.length(); k < kn; k++) {
+ if (Character.isUpperCase(firstName.charAt(k))) {
+ firstNameIndex = k;
+ break;
+ }
+ }
+
+ // Scout forwards and see how many properties we can combine
+ int j = i + 1;
+ if (property.getCategory() != PropertyCategory.PREFERRED
+ && !property.getDescriptor().isDeprecated()) {
+ for (; j < max; j++) {
+ XmlProperty next = properties.get(j);
+ String nextName = next.getName();
+ if (nextName.regionMatches(0, firstName, 0, firstNameIndex)
+ // Also make sure we begin the second word at the next
+ // character; if not, we could have something like
+ // scrollBar
+ // scrollingBehavior
+ && nextName.length() > firstNameIndex
+ && Character.isUpperCase(nextName.charAt(firstNameIndex))) {
+
+ // Deprecated attributes, and preferred attributes, should not
+ // be pushed into normal clusters (preferred stay top-level
+ // and sort to the top, deprecated are all put in the same cluster at
+ // the end)
+
+ if (next.getCategory() == PropertyCategory.PREFERRED) {
+ break;
+ }
+ if (next.getDescriptor().isDeprecated()) {
+ break;
+ }
+
+ // This property should be combined with the previous
+ // property
+ } else {
+ break;
+ }
+ }
+ }
+ if (j - i > 1) {
+ // Combining multiple properties: all the properties from i
+ // through j inclusive
+ XmlProperty[] subprops = new XmlProperty[j - i];
+ for (int k = i, index = 0; k < j; k++, index++) {
+ subprops[index] = properties.get(k);
+ }
+ Arrays.sort(subprops, Property.PRIORITY);
+
+ // See if we can compute a LONGER base than just the first word.
+ // For example, if we have "lineSpacingExtra" and "lineSpacingMultiplier"
+ // we'd like the base to be "lineSpacing", not "line".
+ int common = firstNameIndex;
+ for (int k = firstNameIndex + 1, n = firstName.length(); k < n; k++) {
+ if (Character.isUpperCase(firstName.charAt(k))) {
+ common = k;
+ break;
+ }
+ }
+ if (common > firstNameIndex) {
+ for (int k = 0, n = subprops.length; k < n; k++) {
+ String nextName = subprops[k].getName();
+ if (nextName.regionMatches(0, firstName, 0, common)
+ // Also make sure we begin the second word at the next
+ // character; if not, we could have something like
+ // scrollBar
+ // scrollingBehavior
+ && nextName.length() > common
+ && Character.isUpperCase(nextName.charAt(common))) {
+ // New prefix is okay
+ } else {
+ common = firstNameIndex;
+ break;
+ }
+ }
+ firstNameIndex = common;
+ }
+
+ String base = firstName.substring(0, firstNameIndex);
+ base = DescriptorsUtils.capitalize(base);
+ Property complexProperty = new ComplexProperty(
+ base,
+ "[]",
+ subprops);
+ complexProperty.setPriority(subprops[0].getPriority());
+ //complexProperty.setCategory(PropertyCategory.PREFERRED);
+ collapsed.add(complexProperty);
+ boolean allAdvanced = true;
+ boolean isPreferred = false;
+ for (XmlProperty p : subprops) {
+ p.setParent(complexProperty);
+ PropertyCategory c = p.getCategory();
+ if (c != PropertyCategory.ADVANCED) {
+ allAdvanced = false;
+ }
+ if (c == PropertyCategory.PREFERRED) {
+ isPreferred = true;
+ }
+ }
+ if (isPreferred) {
+ complexProperty.setCategory(PropertyCategory.PREFERRED);
+ } else if (allAdvanced) {
+ complexProperty.setCategory(PropertyCategory.PREFERRED);
+ }
+ } else {
+ // Add the individual properties (usually 1, sometimes 2
+ for (int k = i; k < j; k++) {
+ collapsed.add(properties.get(k));
+ }
+ }
+
+ i = j - 1; // -1: compensate in advance for the for-loop adding 1
+ }
+
+ // Update the complex properties
+ for (String category : categoryToProperties.keySet()) {
+ Collection<Property> subProperties = categoryToProperties.get(category);
+ if (subProperties.size() > 1) {
+ ComplexProperty complex = categoryToProperty.get(category);
+ assert complex != null : category;
+ Property[] subArray = new Property[subProperties.size()];
+ complex.setProperties(subProperties.toArray(subArray));
+ complex.setPriority(subArray[0].getPriority());
+ collapsed.add(complex);
+
+ boolean allAdvanced = true;
+ boolean isPreferred = false;
+ for (Property p : subProperties) {
+ PropertyCategory c = p.getCategory();
+ if (c != PropertyCategory.ADVANCED) {
+ allAdvanced = false;
+ }
+ if (c == PropertyCategory.PREFERRED) {
+ isPreferred = true;
+ }
+ }
+ if (isPreferred) {
+ complex.setCategory(PropertyCategory.PREFERRED);
+ } else if (allAdvanced) {
+ complex.setCategory(PropertyCategory.ADVANCED);
+ }
+ } else if (subProperties.size() == 1) {
+ collapsed.add(subProperties.iterator().next());
+ }
+ }
+
+ if (layoutProperties.size() > 0 || marginProperties != null) {
+ if (marginProperties != null) {
+ XmlProperty[] m =
+ marginProperties.toArray(new XmlProperty[marginProperties.size()]);
+ Property marginProperty = new ComplexProperty(
+ "Margins",
+ "[]",
+ m);
+ layoutProperties.add(marginProperty);
+ marginProperty.setPriority(PRIO_LAST);
+
+ for (XmlProperty p : m) {
+ p.setParent(marginProperty);
+ }
+ }
+ Property[] l = layoutProperties.toArray(new Property[layoutProperties.size()]);
+ Arrays.sort(l, Property.PRIORITY);
+ Property property = new ComplexProperty(
+ "Layout Parameters",
+ "[]",
+ l);
+ for (Property p : l) {
+ if (p instanceof XmlProperty) {
+ ((XmlProperty) p).setParent(property);
+ }
+ }
+ property.setCategory(PropertyCategory.PREFERRED);
+ collapsed.add(property);
+ property.setPriority(PRIO_SECOND);
+ }
+
+ if (deprecatedProperties != null && deprecatedProperties.size() > 0) {
+ Property property = new ComplexProperty(
+ "Deprecated",
+ "(Deprecated Properties)",
+ deprecatedProperties.toArray(new Property[deprecatedProperties.size()]));
+ property.setPriority(PRIO_LAST);
+ collapsed.add(property);
+ }
+
+ Collections.sort(collapsed, Property.PRIORITY);
+
+ return collapsed;
+ }
+
+ PropertyTable getPropertyTable() {
+ return mPropertyTable;
+ }
+
+ @Nullable
+ GraphicalEditorPart getGraphicalEditor() {
+ return mGraphicalEditorPart;
+ }
+
+ // HACK: This should be passed into each property instead
+ public Object getCurrentViewObject() {
+ return mCurrentViewCookie;
+ }
+
+ public void setSortingMode(SortingMode sortingMode) {
+ mSortMode = sortingMode;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyMetadata.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyMetadata.java
new file mode 100644
index 0000000..cdf7664
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyMetadata.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import static com.android.ide.common.layout.LayoutConstants.ATTR_CONTENT_DESCRIPTION;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_HINT;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/** Extra metadata about properties not available from the descriptors (yet) */
+class PropertyMetadata {
+ static boolean isAdvanced(@NonNull String name) {
+ return sAdvanced.contains(name);
+ }
+
+ static boolean isPreferred(@NonNull String name) {
+ return sPreferred.contains(name);
+ }
+
+ @Nullable
+ static String getCategory(@NonNull String name) {
+ //return sCategories.get(name);
+ assert false : "Disabled to save memory since this method is not currently used.";
+ return null;
+ }
+
+ private static final int ADVANCED_MAP_SIZE = 134;
+ private static final Set<String> sAdvanced = new HashSet<String>(ADVANCED_MAP_SIZE);
+ static {
+ // This metadata about which attributes are "advanced" was generated as follows:
+ // First, I ran the sdk/attribute_stats project with the --list argument to dump out
+ // *all* referenced XML attributes found in layouts, run against a bunch of
+ // sample Android code (development/samples, packages/apps, vendor, etc.
+ //
+ // Then I iterated over the LayoutDescriptors' ViewElementDescriptors'
+ // AttributeDescriptors, and basically diffed the two: any attribute descriptor name
+ // which was *not* found in any of the representative layouts is added here
+ // as an advanced property.
+ //
+ // Then I manually edited in some attributes that were referenced in the sample
+ // layouts but which I still consider to be advanced:
+ // -- nothing right now
+
+ // I also manually *removed* some entries from the below list:
+ // drawableBottom (the others, drawableTop, drawableLeft and drawableRight were all
+ // NOT on the list so keep bottom off for symmetry)
+ // rating (useful when you deal with a RatingsBar component)
+
+
+ // Automatically generated, see above:
+ sAdvanced.add("alwaysDrawnWithCache");
+ sAdvanced.add("animationCache");
+ sAdvanced.add("animationDuration");
+ sAdvanced.add("animationResolution");
+ sAdvanced.add("baseline");
+ sAdvanced.add("bufferType");
+ sAdvanced.add("calendarViewShown");
+ sAdvanced.add("completionHint");
+ sAdvanced.add("completionHintView");
+ sAdvanced.add("completionThreshold");
+ sAdvanced.add("cursorVisible");
+ sAdvanced.add("dateTextAppearance");
+ sAdvanced.add("dial");
+ sAdvanced.add("digits");
+ sAdvanced.add("disableChildrenWhenDisabled");
+ sAdvanced.add("disabledAlpha");
+ sAdvanced.add("drawableAlpha");
+ sAdvanced.add("drawableEnd");
+ sAdvanced.add("drawableStart");
+ sAdvanced.add("drawingCacheQuality");
+ sAdvanced.add("dropDownAnchor");
+ sAdvanced.add("dropDownHeight");
+ sAdvanced.add("dropDownHorizontalOffset");
+ sAdvanced.add("dropDownSelector");
+ sAdvanced.add("dropDownVerticalOffset");
+ sAdvanced.add("dropDownWidth");
+ sAdvanced.add("editorExtras");
+ sAdvanced.add("ems");
+ sAdvanced.add("endYear");
+ sAdvanced.add("eventsInterceptionEnabled");
+ sAdvanced.add("fadeDuration");
+ sAdvanced.add("fadeEnabled");
+ sAdvanced.add("fadeOffset");
+ sAdvanced.add("fadeScrollbars");
+ sAdvanced.add("filterTouchesWhenObscured");
+ sAdvanced.add("firstDayOfWeek");
+ sAdvanced.add("flingable");
+ sAdvanced.add("focusedMonthDateColor");
+ sAdvanced.add("foregroundInsidePadding");
+ sAdvanced.add("format");
+ sAdvanced.add("gestureColor");
+ sAdvanced.add("gestureStrokeAngleThreshold");
+ sAdvanced.add("gestureStrokeLengthThreshold");
+ sAdvanced.add("gestureStrokeSquarenessThreshold");
+ sAdvanced.add("gestureStrokeType");
+ sAdvanced.add("gestureStrokeWidth");
+ sAdvanced.add("hand_hour");
+ sAdvanced.add("hand_minute");
+ sAdvanced.add("hapticFeedbackEnabled");
+ sAdvanced.add("id");
+ sAdvanced.add("imeActionId");
+ sAdvanced.add("imeActionLabel");
+ sAdvanced.add("indeterminateDrawable");
+ sAdvanced.add("indeterminateDuration");
+ sAdvanced.add("inputMethod");
+ sAdvanced.add("interpolator");
+ sAdvanced.add("isScrollContainer");
+ sAdvanced.add("keepScreenOn");
+ sAdvanced.add("layerType");
+ sAdvanced.add("layoutDirection");
+ sAdvanced.add("maxDate");
+ sAdvanced.add("minDate");
+ sAdvanced.add("mode");
+ sAdvanced.add("numeric");
+ sAdvanced.add("paddingEnd");
+ sAdvanced.add("paddingStart");
+ sAdvanced.add("persistentDrawingCache");
+ sAdvanced.add("phoneNumber");
+ sAdvanced.add("popupBackground");
+ sAdvanced.add("popupPromptView");
+ sAdvanced.add("privateImeOptions");
+ sAdvanced.add("quickContactWindowSize");
+ //sAdvanced.add("rating");
+ sAdvanced.add("requiresFadingEdge");
+ sAdvanced.add("rotation");
+ sAdvanced.add("rotationX");
+ sAdvanced.add("rotationY");
+ sAdvanced.add("saveEnabled");
+ sAdvanced.add("scaleX");
+ sAdvanced.add("scaleY");
+ sAdvanced.add("scrollX");
+ sAdvanced.add("scrollY");
+ sAdvanced.add("scrollbarAlwaysDrawHorizontalTrack");
+ sAdvanced.add("scrollbarDefaultDelayBeforeFade");
+ sAdvanced.add("scrollbarFadeDuration");
+ sAdvanced.add("scrollbarSize");
+ sAdvanced.add("scrollbarThumbHorizontal");
+ sAdvanced.add("scrollbarThumbVertical");
+ sAdvanced.add("scrollbarTrackHorizontal");
+ sAdvanced.add("scrollbarTrackVertical");
+ sAdvanced.add("secondaryProgress");
+ sAdvanced.add("selectedDateVerticalBar");
+ sAdvanced.add("selectedWeekBackgroundColor");
+ sAdvanced.add("selectionDivider");
+ sAdvanced.add("selectionDividerHeight");
+ sAdvanced.add("showWeekNumber");
+ sAdvanced.add("shownWeekCount");
+ sAdvanced.add("solidColor");
+ sAdvanced.add("soundEffectsEnabled");
+ sAdvanced.add("spinnerMode");
+ sAdvanced.add("spinnersShown");
+ sAdvanced.add("startYear");
+ sAdvanced.add("switchMinWidth");
+ sAdvanced.add("switchPadding");
+ sAdvanced.add("switchTextAppearance");
+ sAdvanced.add("textColorHighlight");
+ sAdvanced.add("textCursorDrawable");
+ sAdvanced.add("textDirection");
+ sAdvanced.add("textEditNoPasteWindowLayout");
+ sAdvanced.add("textEditPasteWindowLayout");
+ sAdvanced.add("textEditSideNoPasteWindowLayout");
+ sAdvanced.add("textEditSidePasteWindowLayout");
+ sAdvanced.add("textEditSuggestionItemLayout");
+ sAdvanced.add("textIsSelectable");
+ sAdvanced.add("textOff");
+ sAdvanced.add("textOn");
+ sAdvanced.add("textScaleX");
+ sAdvanced.add("textSelectHandle");
+ sAdvanced.add("textSelectHandleLeft");
+ sAdvanced.add("textSelectHandleRight");
+ sAdvanced.add("thumbOffset");
+ sAdvanced.add("thumbTextPadding");
+ sAdvanced.add("tint");
+ sAdvanced.add("track");
+ sAdvanced.add("transformPivotX");
+ sAdvanced.add("transformPivotY");
+ sAdvanced.add("translationX");
+ sAdvanced.add("translationY");
+ sAdvanced.add("uncertainGestureColor");
+ sAdvanced.add("unfocusedMonthDateColor");
+ sAdvanced.add("unselectedAlpha");
+ sAdvanced.add("verticalScrollbarPosition");
+ sAdvanced.add("weekDayTextAppearance");
+ sAdvanced.add("weekNumberColor");
+ sAdvanced.add("weekSeparatorLineColor");
+
+ assert sAdvanced.size() == ADVANCED_MAP_SIZE : sAdvanced.size();
+
+ }
+
+ private static final int PREFERRED_MAP_SIZE = 7;
+ private static final Set<String> sPreferred = new HashSet<String>(PREFERRED_MAP_SIZE);
+ static {
+ // Manual registrations of attributes that should be treated as preferred if
+ // they are available on a widget even if they don't show up in the top 10% of
+ // usages (which the view metadata provides)
+ sPreferred.add(ATTR_TEXT);
+ sPreferred.add(ATTR_CONTENT_DESCRIPTION);
+ sPreferred.add(ATTR_HINT);
+ sPreferred.add("indeterminate");
+ sPreferred.add("progress");
+ sPreferred.add("rating");
+ sPreferred.add("max");
+ assert sPreferred.size() == PREFERRED_MAP_SIZE : sPreferred.size();
+ }
+
+ /*
+ private static final int CATEGORY_MAP_SIZE = 62;
+ private static final Map<String, String> sCategories =
+ new HashMap<String, String>(CATEGORY_MAP_SIZE);
+ static {
+ sCategories.put("requiresFadingEdge", "Scrolling");
+ sCategories.put("fadingEdgeLength", "Scrolling");
+ sCategories.put("scrollbarSize", "Scrolling");
+ sCategories.put("scrollbarThumbVertical", "Scrolling");
+ sCategories.put("scrollbarThumbHorizontal", "Scrolling");
+ sCategories.put("scrollbarTrackHorizontal", "Scrolling");
+ sCategories.put("scrollbarTrackVertical", "Scrolling");
+ sCategories.put("scrollbarAlwaysDrawHorizontalTrack", "Scrolling");
+ sCategories.put("scrollbarAlwaysDrawVerticalTrack", "Scrolling");
+ sCategories.put("scrollViewStyle", "Scrolling");
+ sCategories.put("scrollbars", "Scrolling");
+ sCategories.put("scrollingCache", "Scrolling");
+ sCategories.put("scrollHorizontally", "Scrolling");
+ sCategories.put("scrollbarFadeDuration", "Scrolling");
+ sCategories.put("scrollbarDefaultDelayBeforeFade", "Scrolling");
+ sCategories.put("fastScrollEnabled", "Scrolling");
+ sCategories.put("smoothScrollbar", "Scrolling");
+ sCategories.put("isScrollContainer", "Scrolling");
+ sCategories.put("fadeScrollbars", "Scrolling");
+ sCategories.put("overScrollMode", "Scrolling");
+ sCategories.put("overScrollHeader", "Scrolling");
+ sCategories.put("overScrollFooter", "Scrolling");
+ sCategories.put("verticalScrollbarPosition", "Scrolling");
+ sCategories.put("fastScrollAlwaysVisible", "Scrolling");
+ sCategories.put("fastScrollThumbDrawable", "Scrolling");
+ sCategories.put("fastScrollPreviewBackgroundLeft", "Scrolling");
+ sCategories.put("fastScrollPreviewBackgroundRight", "Scrolling");
+ sCategories.put("fastScrollTrackDrawable", "Scrolling");
+ sCategories.put("fastScrollOverlayPosition", "Scrolling");
+ sCategories.put("horizontalScrollViewStyle", "Scrolling");
+ sCategories.put("fastScrollTextColor", "Scrolling");
+ sCategories.put("scrollbarSize", "Scrolling");
+ sCategories.put("scrollbarSize", "Scrolling");
+ sCategories.put("scrollbarSize", "Scrolling");
+ sCategories.put("scrollbarSize", "Scrolling");
+ sCategories.put("scrollbarSize", "Scrolling");
+
+ // TODO: All the styles: radioButtonStyle, ratingBarStyle, progressBarStyle, ...
+
+ sCategories.put("focusable", "Focus");
+ sCategories.put("focusableInTouchMode", "Focus");
+ sCategories.put("nextFocusLeft", "Focus");
+ sCategories.put("nextFocusRight", "Focus");
+ sCategories.put("nextFocusUp", "Focus");
+ sCategories.put("nextFocusDown", "Focus");
+ sCategories.put("descendantFocusability", "Focus");
+ sCategories.put("selectAllOnFocus", "Focus");
+ sCategories.put("nextFocusForward", "Focus");
+ sCategories.put("colorFocusedHighlight", "Focus");
+
+ sCategories.put("rotation", "Transforms");
+ sCategories.put("scrollX", "Transforms");
+ sCategories.put("scrollY", "Transforms");
+ sCategories.put("rotationX", "Transforms");
+ sCategories.put("rotationY", "Transforms");
+ sCategories.put("transformPivotX", "Transforms");
+ sCategories.put("transformPivotY", "Transforms");
+ sCategories.put("translationX", "Transforms");
+ sCategories.put("translationY", "Transforms");
+ sCategories.put("scaleX", "Transforms");
+ sCategories.put("scaleY", "Transforms");
+
+ sCategories.put("width", "Size");
+ sCategories.put("height", "Size");
+ sCategories.put("minWidth", "Size");
+ sCategories.put("minHeight", "Size");
+
+ sCategories.put("longClickable", "Clicks");
+ sCategories.put("onClick", "Clicks");
+ sCategories.put("clickable", "Clicks");
+ sCategories.put("hapticFeedbackEnabled", "Clicks");
+
+ sCategories.put("duplicateParentState", "State");
+ sCategories.put("addStatesFromChildren", "State");
+
+ assert sCategories.size() == CATEGORY_MAP_SIZE : sCategories.size();
+ }
+ */
+
+// private static final int PRIO_CLZ_LAYOUT = 1000;
+// private static final int PRIO_CLZ_TEXT = 2000;
+// private static final int PRIO_CLZ_DRAWABLE = 3000;
+// private static final int PRIO_CLZ_ANIMATION = 4000;
+// private static final int PRIO_CLZ_FOCUS = 5000;
+//
+// private static final int PRIORITY_MAP_SIZE = 100;
+// private static final Map<String, Integer> sPriorities =
+// new HashMap<String, Integer>(PRIORITY_MAP_SIZE);
+// static {
+// // TODO: I should put all the properties roughly based on their original order: this
+// // will correspond to the rough order they came in with
+// // TODO: How can I make similar complex properties show up adjacent; e.g. min and max
+// sPriorities.put("min", PRIO_CLZ_LAYOUT);
+// sPriorities.put("max", PRIO_CLZ_LAYOUT);
+//
+// assert sPriorities.size() == PRIORITY_MAP_SIZE : sPriorities.size();
+// }
+
+ // TODO: Emit metadata into a file
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertySheetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertySheetPage.java
new file mode 100644
index 0000000..4a33223
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertySheetPage.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory.SortingMode;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IStatusLineManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.part.Page;
+import org.eclipse.ui.views.properties.IPropertySheetPage;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.table.IPropertyExceptionHandler;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Property sheet page used when the graphical layout editor is chosen
+ */
+public class PropertySheetPage extends Page implements IPropertySheetPage, IUiUpdateListener {
+ private PropertyTable mPropertyTable;
+ private final GraphicalEditorPart mEditor;
+ private PropertyFactory mPropertyFactory;
+ private Property mActiveProperty;
+ private Action mDefaultValueAction;
+ private Action mShowAdvancedPropertiesAction;
+ private Action mSortAlphaAction;
+ private Action mCollapseAll;
+ private Action mExpandAll;
+ private List<CanvasViewInfo> mSelection;
+
+ private static final String EXPAND_DISABLED_ICON = "expandall-disabled"; //$NON-NLS-1$
+ private static final String EXPAND_ICON = "expandall"; //$NON-NLS-1$
+ private static final String DEFAULT_ICON = "properties_default"; //$NON-NLS-1$
+ private static final String ADVANCED_ICON = "filter_advanced_properties"; //$NON-NLS-1$
+ private static final String ALPHA_ICON = "sort_alpha"; //$NON-NLS-1$
+ // TODO: goto-definition.png
+
+ /**
+ * Constructs a new {@link PropertySheetPage} associated with the given
+ * editor
+ *
+ * @param editor the editor associated with this property sheet page
+ */
+ public PropertySheetPage(GraphicalEditorPart editor) {
+ mEditor = editor;
+ }
+
+ PropertyFactory getPropertyFactory() {
+ if (mPropertyFactory == null) {
+ assert mPropertyTable != null;
+ mPropertyFactory = new PropertyFactory(mEditor, mPropertyTable);
+ }
+
+ return mPropertyFactory;
+ }
+
+ @Override
+ public void createControl(Composite parent) {
+ mPropertyTable = new PropertyTable(parent, SWT.NONE);
+ mPropertyTable.setExceptionHandler(new IPropertyExceptionHandler() {
+ @Override
+ public void handle(Throwable e) {
+ AdtPlugin.log(e, null);
+ }
+ });
+ mPropertyTable.setDefaultCollapsedNames(Arrays.asList(
+ "Deprecated",
+ "Layout Parameters|Margins"));
+
+ createActions();
+ setPropertyTableContextMenu();
+ }
+
+ @Override
+ public void selectionChanged(IWorkbenchPart part, ISelection selection) {
+ if (selection instanceof TreeSelection
+ && mPropertyTable != null && !mPropertyTable.isDisposed()) {
+ TreeSelection treeSelection = (TreeSelection) selection;
+
+ stopTrackingSelection();
+
+ if (treeSelection.isEmpty()) {
+ mSelection = Collections.emptyList();
+ } else {
+ int selectionCount = treeSelection.size();
+ List<CanvasViewInfo> newSelection = new ArrayList<CanvasViewInfo>(selectionCount);
+ Iterator<?> iterator = treeSelection.iterator();
+ while (iterator.hasNext()) {
+ Object next = iterator.next();
+ if (next instanceof CanvasViewInfo) {
+ CanvasViewInfo info = (CanvasViewInfo) next;
+ newSelection.add(info);
+ }
+ }
+ mSelection = newSelection;
+ }
+
+ startTrackingSelection();
+
+ refreshProperties();
+ }
+ }
+
+ @Override
+ public void dispose() {
+ stopTrackingSelection();
+ super.dispose();
+ }
+
+ private void startTrackingSelection() {
+ if (mSelection != null && !mSelection.isEmpty()) {
+ for (CanvasViewInfo item : mSelection) {
+ UiViewElementNode node = item.getUiViewNode();
+ if (node != null) {
+ node.addUpdateListener(this);
+ }
+ }
+ }
+ }
+
+ private void stopTrackingSelection() {
+ if (mSelection != null && !mSelection.isEmpty()) {
+ for (CanvasViewInfo item : mSelection) {
+ UiViewElementNode node = item.getUiViewNode();
+ if (node != null) {
+ node.removeUpdateListener(this);
+ }
+ }
+ }
+ mSelection = null;
+ }
+
+ // Implements IUiUpdateListener
+ @Override
+ public void uiElementNodeUpdated(UiElementNode node, UiUpdateState state) {
+ refreshProperties();
+ }
+
+ @Override
+ public Control getControl() {
+ return mPropertyTable;
+ }
+
+ @Override
+ public void setFocus() {
+ mPropertyTable.setFocus();
+ }
+
+ @Override
+ public void makeContributions(IMenuManager menuManager,
+ IToolBarManager toolBarManager, IStatusLineManager statusLineManager) {
+ toolBarManager.add(mShowAdvancedPropertiesAction);
+ toolBarManager.add(new Separator());
+ toolBarManager.add(mSortAlphaAction);
+ toolBarManager.add(new Separator());
+ toolBarManager.add(mDefaultValueAction);
+ toolBarManager.add(new Separator());
+ toolBarManager.add(mExpandAll);
+ toolBarManager.add(mCollapseAll);
+ toolBarManager.add(new Separator());
+ }
+
+ private void createActions() {
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ IconFactory iconFactory = IconFactory.getInstance();
+
+ mExpandAll = new PropertySheetAction(
+ IAction.AS_PUSH_BUTTON,
+ "Expand All",
+ ACTION_EXPAND,
+ iconFactory.getImageDescriptor(EXPAND_ICON),
+ iconFactory.getImageDescriptor(EXPAND_DISABLED_ICON));
+
+ mCollapseAll = new PropertySheetAction(
+ IAction.AS_PUSH_BUTTON,
+ "Collapse All",
+ ACTION_COLLAPSE,
+ sharedImages.getImageDescriptor(ISharedImages.IMG_ELCL_COLLAPSEALL),
+ sharedImages.getImageDescriptor(ISharedImages.IMG_ELCL_COLLAPSEALL_DISABLED));
+
+ mShowAdvancedPropertiesAction = new PropertySheetAction(
+ IAction.AS_CHECK_BOX,
+ "Show Advanced Properties",
+ ACTION_SHOW_ADVANCED,
+ iconFactory.getImageDescriptor(ADVANCED_ICON),
+ null);
+
+ mSortAlphaAction = new PropertySheetAction(
+ IAction.AS_CHECK_BOX,
+ "Sort Alphabetically",
+ ACTION_SORT_ALPHA,
+ iconFactory.getImageDescriptor(ALPHA_ICON),
+ null);
+
+ mDefaultValueAction = new PropertySheetAction(
+ IAction.AS_PUSH_BUTTON,
+ "Restore Default Value",
+ ACTION_DEFAULT_VALUE,
+ iconFactory.getImageDescriptor(DEFAULT_ICON),
+ null);
+
+ // Listen on the selection in the property sheet so we can update the
+ // Restore Default Value action
+ ISelectionChangedListener listener = new ISelectionChangedListener() {
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ StructuredSelection selection = (StructuredSelection) event.getSelection();
+ mActiveProperty = (Property) selection.getFirstElement();
+ updateDefaultValueAction();
+ }
+ };
+ mPropertyTable.addSelectionChangedListener(listener);
+ }
+
+ /**
+ * Updates the state of {@link #mDefaultValueAction}.
+ */
+ private void updateDefaultValueAction() {
+ if (mActiveProperty != null) {
+ try {
+ mDefaultValueAction.setEnabled(mActiveProperty.isModified());
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+ } else {
+ mDefaultValueAction.setEnabled(false);
+ }
+ }
+
+ /**
+ * Sets the context menu for {@link #mPropertyTable}.
+ */
+ private void setPropertyTableContextMenu() {
+ final MenuManager manager = new MenuManager();
+ manager.setRemoveAllWhenShown(true);
+ manager.addMenuListener(new IMenuListener() {
+ @Override
+ public void menuAboutToShow(IMenuManager m) {
+ // dispose items to avoid caching
+ for (MenuItem item : manager.getMenu().getItems()) {
+ item.dispose();
+ }
+ // apply new items
+ fillContextMenu();
+ }
+
+ private void fillContextMenu() {
+ manager.add(mDefaultValueAction);
+ manager.add(mSortAlphaAction);
+ manager.add(mShowAdvancedPropertiesAction);
+ }
+ });
+
+ mPropertyTable.setMenu(manager.createContextMenu(mPropertyTable));
+ }
+
+ /**
+ * Shows {@link Property}'s of current objects.
+ */
+ private void refreshProperties() {
+ PropertyFactory factory = getPropertyFactory();
+ mPropertyTable.setInput(factory.getProperties(mSelection));
+ updateDefaultValueAction();
+ }
+
+ // ---- Actions ----
+
+ private static final int ACTION_DEFAULT_VALUE = 1;
+ private static final int ACTION_SHOW_ADVANCED = 2;
+ private static final int ACTION_COLLAPSE = 3;
+ private static final int ACTION_EXPAND = 4;
+ private static final int ACTION_SORT_ALPHA = 5;
+
+ private class PropertySheetAction extends Action {
+ private final int mAction;
+
+ private PropertySheetAction(int style, String label, int action,
+ ImageDescriptor imageDesc, ImageDescriptor disabledImageDesc) {
+ super(label, style);
+ mAction = action;
+ setImageDescriptor(imageDesc);
+ if (disabledImageDesc != null) {
+ setDisabledImageDescriptor(disabledImageDesc);
+ }
+ setToolTipText(label);
+ }
+
+ @Override
+ public void run() {
+ switch (mAction) {
+ case ACTION_COLLAPSE: {
+ mPropertyTable.collapseAll();
+ break;
+ }
+ case ACTION_EXPAND: {
+ mPropertyTable.expandAll();
+ break;
+ }
+ case ACTION_SHOW_ADVANCED: {
+ boolean show = mShowAdvancedPropertiesAction.isChecked();
+ mPropertyTable.setShowAdvancedProperties(show);
+ break;
+ }
+ case ACTION_SORT_ALPHA: {
+ boolean isAlphabetical = mSortAlphaAction.isChecked();
+ getPropertyFactory().setSortingMode(
+ isAlphabetical ? SortingMode.ALPHABETICAL : PropertyFactory.DEFAULT_MODE);
+ refreshProperties();
+ break;
+ }
+ case ACTION_DEFAULT_VALUE:
+ try {
+ mActiveProperty.setValue(Property.UNKNOWN_VALUE);
+ } catch (Exception e) {
+ // Ignore warnings from setters
+ }
+ break;
+ default:
+ assert false : mAction;
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java
new file mode 100644
index 0000000..19dc0bc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_RESOURCE_REF;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_RESOURCE_REF;
+import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG;
+
+import com.android.ide.common.resources.ResourceItem;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.resources.ResourceType;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.fieldassist.ContentProposal;
+import org.eclipse.jface.fieldassist.IContentProposal;
+import org.eclipse.jface.fieldassist.IContentProposalProvider;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Resource value completion for the given property
+ * <p>
+ * TODO:
+ * <ul>
+ * <li>also offer other values seen in the app
+ * <li>also offer previously set values for this property
+ * <li>also complete on properties
+ * </ul>
+ */
+class ResourceValueCompleter implements IContentProposalProvider {
+ protected final XmlProperty xmlProperty;
+
+ ResourceValueCompleter(XmlProperty xmlProperty) {
+ this.xmlProperty = xmlProperty;
+ }
+
+ @Override
+ public IContentProposal[] getProposals(String contents, int position) {
+ if (contents.startsWith(PREFIX_RESOURCE_REF)) {
+ CommonXmlEditor editor = this.xmlProperty.getXmlEditor();
+ if (editor != null) {
+ String[] matches = computeResourceStringMatches(
+ editor,
+ this.xmlProperty.mDescriptor, contents.substring(0, position));
+ List<IContentProposal> proposals = null;
+ if (matches != null && matches.length > 0) {
+ proposals = new ArrayList<IContentProposal>(matches.length);
+ for (String match : matches) {
+ proposals.add(new ContentProposal(match));
+ }
+ return proposals.toArray(new IContentProposal[proposals.size()]);
+ }
+ }
+ }
+
+ return new IContentProposal[0];
+ }
+
+ /**
+ * Similar to {@link UiResourceAttributeNode#computeResourceStringMatches}
+ * but computes complete results up front rather than dividing it up into
+ * smaller chunks like @{code @android:}, {@code string/}, and {@code ok}.
+ */
+ static String[] computeResourceStringMatches(AndroidXmlEditor editor,
+ AttributeDescriptor attributeDescriptor, String prefix) {
+ List<String> results = new ArrayList<String>(200);
+
+ // System matches: only do this if the value already matches at least @a,
+ // and doesn't start with something that can't possibly be @android
+ if (prefix.startsWith("@a") && //$NON-NLS-1$
+ prefix.regionMatches(true /* ignoreCase */, 0, PREFIX_ANDROID_RESOURCE_REF, 0,
+ Math.min(prefix.length() - 1, PREFIX_ANDROID_RESOURCE_REF.length()))) {
+ AndroidTargetData data = editor.getTargetData();
+ if (data != null) {
+ ResourceRepository repository = data.getFrameworkResources();
+ addMatches(repository, prefix, true /* isSystem */, results);
+ }
+ }
+
+ // When completing project resources skip framework resources unless
+ // the prefix possibly completes both, such as "@an" which can match
+ // both the project resource @animator as well as @android:string
+ if (!prefix.startsWith("@and")) { //$NON-NLS-1$
+ IProject project = editor.getProject();
+ if (project != null) {
+ // get the resource repository for this project and the system resources.
+ ResourceManager manager = ResourceManager.getInstance();
+ ResourceRepository repository = manager.getProjectResources(project);
+ if (repository != null) {
+ // We have a style name and a repository. Find all resources that match this
+ // type and recreate suggestions out of them.
+ addMatches(repository, prefix, false /* isSystem */, results);
+ }
+
+ }
+ }
+
+ if (attributeDescriptor != null) {
+ UiResourceAttributeNode.sortAttributeChoices(attributeDescriptor, results);
+ } else {
+ Collections.sort(results);
+ }
+
+ return results.toArray(new String[results.size()]);
+ }
+
+ private static void addMatches(ResourceRepository repository, String prefix, boolean isSystem,
+ List<String> results) {
+ int typeStart = isSystem
+ ? PREFIX_ANDROID_RESOURCE_REF.length() : PREFIX_RESOURCE_REF.length();
+
+ for (ResourceType type : repository.getAvailableResourceTypes()) {
+ if (prefix.regionMatches(typeStart, type.getName(), 0,
+ Math.min(type.getName().length(), prefix.length() - typeStart))) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(PREFIX_RESOURCE_REF);
+
+ if (type == ResourceType.ID && prefix.startsWith(NEW_ID_PREFIX)) {
+ sb.append('+');
+ }
+
+ if (isSystem) {
+ sb.append(ANDROID_PKG).append(':');
+ }
+
+ sb.append(type.getName()).append('/');
+ String base = sb.toString();
+
+ int nameStart = typeStart + type.getName().length() + 1; // +1: add "/" divider
+ String namePrefix =
+ prefix.length() <= nameStart ? "" : prefix.substring(nameStart);
+ for (ResourceItem item : repository.getResourceItemsOfType(type)) {
+ String name = item.getName();
+ if (AdtUtils.startsWithIgnoreCase(name, namePrefix)) {
+ results.add(base + name);
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/StringXmlPropertyDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/StringXmlPropertyDialog.java
new file mode 100644
index 0000000..3fb72a9
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/StringXmlPropertyDialog.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.editor.string.StringPropertyDialog;
+
+class StringXmlPropertyDialog extends StringPropertyDialog {
+ StringXmlPropertyDialog(Shell parentShell, Property property) throws Exception {
+ super(parentShell, property);
+ }
+
+ @Override
+ protected boolean isMultiLine() {
+ return false;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlProperty.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlProperty.java
new file mode 100644
index 0000000..c846ea1
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlProperty.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.common.api.IAttributeInfo.Format;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ViewHierarchy;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+
+import org.eclipse.jface.fieldassist.IContentProposal;
+import org.eclipse.jface.fieldassist.IContentProposalProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.views.properties.IPropertyDescriptor;
+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.PropertyTooltipProvider;
+import org.eclipse.wb.internal.core.model.property.table.PropertyTooltipTextProvider;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.util.EnumSet;
+import java.util.Map;
+
+/**
+ * An Android XML property
+ */
+class XmlProperty extends Property {
+ private PropertyFactory mFactory;
+ final AttributeDescriptor mDescriptor;
+ private UiViewElementNode mNode;
+ private Property mParent;
+
+ XmlProperty(
+ @NonNull PropertyEditor editor,
+ @NonNull PropertyFactory factory,
+ @NonNull UiViewElementNode node,
+ @NonNull AttributeDescriptor descriptor) {
+ super(editor);
+ mFactory = factory;
+ mNode = node;
+ mDescriptor = descriptor;
+ }
+
+ public PropertyFactory getFactory() {
+ return mFactory;
+ }
+
+ public UiViewElementNode getNode() {
+ return mNode;
+ }
+
+ public AttributeDescriptor getDescriptor() {
+ return mDescriptor;
+ }
+
+ @Override
+ public String getName() {
+ return mDescriptor.getXmlLocalName();
+ }
+
+ @Override
+ public String getTitle() {
+ String name = mDescriptor.getXmlLocalName();
+ int nameLength = name.length();
+
+ if (name.startsWith(ATTR_LAYOUT_PREFIX)) {
+ if (name.startsWith(ATTR_LAYOUT_MARGIN)
+ && nameLength > ATTR_LAYOUT_MARGIN.length()) {
+ name = name.substring(ATTR_LAYOUT_MARGIN.length());
+ } else {
+ name = name.substring(ATTR_LAYOUT_PREFIX.length());
+ }
+ }
+
+ // Capitalize
+ name = DescriptorsUtils.capitalize(name);
+
+ // If we're nested within a complex property, say "Line Spacing", don't
+ // include "Line Spacing " as a prefix for each property here
+ if (mParent != null) {
+ String parentTitle = mParent.getTitle();
+ if (name.startsWith(parentTitle)) {
+ int parentTitleLength = parentTitle.length();
+ if (parentTitleLength < nameLength) {
+ if (nameLength > parentTitleLength &&
+ Character.isWhitespace(name.charAt(parentTitleLength))) {
+ parentTitleLength++;
+ }
+ name = name.substring(parentTitleLength);
+ }
+ }
+ }
+
+ return name;
+ }
+
+ @Override
+ public <T> T getAdapter(Class<T> adapter) {
+ // tooltip
+ if (adapter == PropertyTooltipProvider.class) {
+ return adapter.cast(new PropertyTooltipTextProvider() {
+ @Override
+ protected String getText(Property p) throws Exception {
+ if (mDescriptor instanceof IPropertyDescriptor) {
+ IPropertyDescriptor d = (IPropertyDescriptor) mDescriptor;
+ return d.getDescription();
+ }
+
+ return null;
+ }
+ });
+ } else if (adapter == IContentProposalProvider.class) {
+ IAttributeInfo info = mDescriptor.getAttributeInfo();
+ if (info != null) {
+ EnumSet<Format> formats = info.getFormats();
+ if (formats.contains(Format.FLAG)) {
+ return adapter.cast(new FlagValueCompleter(this, info.getFlagValues()));
+ } else if (formats.contains(Format.ENUM)) {
+ return adapter.cast(new FlagValueCompleter(this, info.getEnumValues()));
+ }
+ }
+ // Fallback: complete values on resource values
+ return adapter.cast(new ResourceValueCompleter(this));
+ } else if (adapter == ILabelProvider.class) {
+ return adapter.cast(new LabelProvider() {
+ @Override
+ public Image getImage(Object element) {
+ return AdtPlugin.getAndroidLogo();
+ }
+
+ @Override
+ public String getText(Object element) {
+ return ((IContentProposal) element).getLabel();
+ }
+ });
+ }
+ return super.getAdapter(adapter);
+ }
+
+ @Override
+ public boolean isModified() throws Exception {
+ Object s = null;
+ try {
+ Element element = (Element) mNode.getXmlNode();
+ String name = mDescriptor.getXmlLocalName();
+ String uri = mDescriptor.getNamespaceUri();
+ if (uri != null) {
+ return element.hasAttributeNS(uri, name);
+ } else {
+ return element.hasAttribute(name);
+ }
+ } catch (Exception e) {
+ // pass
+ }
+ return s != null && s.toString().length() > 0;
+ }
+
+ public String getStringValue() {
+ Element element = (Element) mNode.getXmlNode();
+ String name = mDescriptor.getXmlLocalName();
+ String uri = mDescriptor.getNamespaceUri();
+ Attr attr;
+ if (uri != null) {
+ attr = element.getAttributeNodeNS(uri, name);
+ } else {
+ attr = element.getAttributeNode(name);
+ }
+ if (attr != null) {
+ return attr.getValue();
+ }
+
+ Object viewObject = getFactory().getCurrentViewObject();
+ if (viewObject != null) {
+ ViewHierarchy views = getGraphicalEditor().getCanvasControl().getViewHierarchy();
+ Map<String, String> defaultProperties = views.getDefaultProperties(viewObject);
+ if (defaultProperties != null) {
+ return defaultProperties.get(name);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public Object getValue() throws Exception {
+ return getStringValue();
+ }
+
+ @Override
+ public void setValue(Object value) throws Exception {
+ CommonXmlEditor editor = getXmlEditor();
+ if (editor == null) {
+ return;
+ }
+ final String attribute = mDescriptor.getXmlLocalName();
+ final String xmlValue = value != null && value != UNKNOWN_VALUE ? value.toString() : null;
+ editor.wrapUndoEditXmlModel(
+ String.format("Set \"%1$s\" to \"%2$s\"", attribute, xmlValue),
+ new Runnable() {
+ @Override
+ public void run() {
+ mNode.setAttributeValue(attribute,
+ mDescriptor.getNamespaceUri(), xmlValue, true /*override*/);
+ mNode.commitDirtyAttributesToXml();
+ }
+ });
+ }
+
+ @Override
+ public Property getComposite(Property[] properties) {
+ return XmlPropertyComposite.create(properties);
+ }
+
+ @Nullable
+ GraphicalEditorPart getGraphicalEditor() {
+ return mFactory.getGraphicalEditor();
+ }
+
+ @Nullable CommonXmlEditor getXmlEditor() {
+ GraphicalEditorPart graphicalEditor = getGraphicalEditor();
+ if (graphicalEditor != null) {
+ return graphicalEditor.getEditorDelegate().getEditor();
+ }
+
+ return null;
+ }
+
+ public Property getParent() {
+ return mParent;
+ }
+
+ public void setParent(Property parent) {
+ mParent = parent;
+ }
+
+ @Override
+ public String toString() {
+ return getName() + ":" + getPriority();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyComposite.java
new file mode 100644
index 0000000..7abc91c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyComposite.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import com.google.common.base.Objects;
+
+import org.eclipse.wb.internal.core.model.property.Property;
+
+import java.util.Arrays;
+
+/**
+ * Property holding multiple instances of the same {@link XmlProperty} (but
+ * bound to difference objects. This is used when multiple objects are selected
+ * in the layout editor and the common properties are shown; editing a value
+ * will (via {@link #setValue(Object)}) set it on all selected objects.
+ * <p>
+ * Similar to
+ * org.eclipse.wb.internal.core.model.property.GenericPropertyComposite
+ */
+class XmlPropertyComposite extends XmlProperty {
+ private static final Object NO_VALUE = new Object();
+
+ private final XmlProperty[] mProperties;
+
+ public XmlPropertyComposite(XmlProperty primary, XmlProperty[] properties) {
+ super(
+ primary.getEditor(),
+ primary.getFactory(),
+ primary.getNode(),
+ primary.getDescriptor());
+ mProperties = properties;
+ }
+
+ @Override
+ public String getTitle() {
+ return mProperties[0].getTitle();
+ }
+
+ @Override
+ public int hashCode() {
+ return mProperties.length;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (obj instanceof XmlPropertyComposite) {
+ XmlPropertyComposite property = (XmlPropertyComposite) obj;
+ return Arrays.equals(mProperties, property.mProperties);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isModified() throws Exception {
+ for (Property property : mProperties) {
+ if (property.isModified()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public Object getValue() throws Exception {
+ Object value = NO_VALUE;
+ for (Property property : mProperties) {
+ Object propertyValue = property.getValue();
+ if (value == NO_VALUE) {
+ value = propertyValue;
+ } else if (!Objects.equal(value, propertyValue)) {
+ return UNKNOWN_VALUE;
+ }
+ }
+
+ return value;
+ }
+
+ @Override
+ public void setValue(final Object value) throws Exception {
+ // TBD: Wrap in ExecutionUtils.run?
+ for (Property property : mProperties) {
+ property.setValue(value);
+ }
+ }
+
+ public static XmlPropertyComposite create(Property... properties) {
+ // Cast from Property into XmlProperty
+ XmlProperty[] xmlProperties = new XmlProperty[properties.length];
+ for (int i = 0; i < properties.length; i++) {
+ Property property = properties[i];
+ xmlProperties[i] = (XmlProperty) property;
+ }
+
+ XmlPropertyComposite composite = new XmlPropertyComposite(xmlProperties[0], xmlProperties);
+ composite.setCategory(xmlProperties[0].getCategory());
+ return composite;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java
new file mode 100644
index 0000000..628cda6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+
+import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_PNG;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.common.api.IAttributeInfo.Format;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.ResourceResolver;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtUtils;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
+import com.android.ide.eclipse.adt.internal.ui.ReferenceChooserDialog;
+import com.android.ide.eclipse.adt.internal.ui.ResourceChooser;
+import com.android.ide.eclipse.adt.internal.ui.ResourcePreviewHelper;
+import com.android.resources.ResourceType;
+import com.google.common.collect.Maps;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.graphics.Color;
+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.RGB;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.wb.draw2d.IColorConstants;
+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.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.wb.internal.core.utils.ui.DrawUtils;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Special property editor used for the {@link XmlProperty} instances which handles
+ * editing the XML properties, rendering defaults by looking up the actual colors and images,
+ */
+class XmlPropertyEditor extends AbstractTextPropertyEditor {
+ public static final XmlPropertyEditor INSTANCE = new XmlPropertyEditor();
+ private static final int SAMPLE_SIZE = 10;
+ private static final int SAMPLE_MARGIN = 3;
+
+ protected XmlPropertyEditor() {
+ }
+
+ private final PropertyEditorPresentation mPresentation =
+ new ButtonPropertyEditorPresentation() {
+ @Override
+ protected void onClick(PropertyTable propertyTable, Property property) throws Exception {
+ openDialog(propertyTable, property);
+ }
+ };
+
+ @Override
+ public PropertyEditorPresentation getPresentation() {
+ return mPresentation;
+ }
+
+ @Override
+ public String getText(Property property) throws Exception {
+ Object value = property.getValue();
+ if (value instanceof String) {
+ return (String) value;
+ }
+ return null;
+ }
+
+ @Override
+ protected String getEditorText(Property property) throws Exception {
+ return getText(property);
+ }
+
+ @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) {
+ ResourceValue resValue = null;
+ String resolvedText = null;
+
+ // TODO: Use the constants for @, ?, @android: etc
+ if (text.startsWith("@") || text.startsWith("?")) { //$NON-NLS-1$ //$NON-NLS-2$
+ // Yes, try to resolve it in order to show better info
+ XmlProperty xmlProperty = (XmlProperty) property;
+ ResourceResolver resolver = xmlProperty.getGraphicalEditor().getResourceResolver();
+ boolean isFramework = text.startsWith("@android:") || text.startsWith("?android:");
+ resValue = resolver.findResValue(text, isFramework);
+ while (resValue != null && resValue.getValue() != null) {
+ String value = resValue.getValue();
+ if (value.startsWith("@") || value.startsWith("?")) {
+ // TODO: do I have to strip off the @ too?
+ isFramework = isFramework || value.startsWith("@android:") || value.startsWith("?android:");;
+ ResourceValue v = resolver.findResValue(text, isFramework);
+ if (v != null && !value.equals(v.getValue())) {
+ resValue = v;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ } else if (text.startsWith("#") && text.matches("#\\p{XDigit}+")) { //$NON-NLS-1$
+ resValue = new ResourceValue(ResourceType.COLOR, property.getName(), text, false);
+ }
+
+ if (resValue != null && resValue.getValue() != null) {
+ String value = resValue.getValue();
+ // Decide whether it's a color, an image, a nine patch etc
+ // and decide how to render it
+ if (value.startsWith("#") || value.endsWith(DOT_XML) //$NON-NLS-1$
+ && value.contains("res/color")) { //$NON-NLS-1$ // TBD: File.separator?
+ XmlProperty xmlProperty = (XmlProperty) property;
+ ResourceResolver resolver =
+ xmlProperty.getGraphicalEditor().getResourceResolver();
+ RGB rgb = ResourceHelper.resolveColor(resolver, resValue);
+ if (rgb != null) {
+ Color color = new Color(gc.getDevice(), rgb);
+ // draw color sample
+ Color oldBackground = gc.getBackground();
+ Color oldForeground = gc.getForeground();
+ try {
+ int width_c = SAMPLE_SIZE;
+ int height_c = SAMPLE_SIZE;
+ int x_c = x;
+ int y_c = y + (height - height_c) / 2;
+ // update rest bounds
+ int delta = SAMPLE_SIZE + SAMPLE_MARGIN;
+ x += delta;
+ width -= delta;
+ // fill
+ gc.setBackground(color);
+ gc.fillRectangle(x_c, y_c, width_c, height_c);
+ // draw line
+ gc.setForeground(IColorConstants.gray);
+ gc.drawRectangle(x_c, y_c, width_c, height_c);
+ } finally {
+ gc.setBackground(oldBackground);
+ gc.setForeground(oldForeground);
+ }
+ color.dispose();
+ }
+ } else {
+ Image swtImage = null;
+ if (value.endsWith(DOT_XML) && value.contains("res/drawable")) { // TBD: Filesep?
+ Map<String, Image> cache = getImageCache(property);
+ swtImage = cache.get(value);
+ if (swtImage == null) {
+ XmlProperty xmlProperty = (XmlProperty) property;
+ GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor();
+ RenderService service = RenderService.create(graphicalEditor);
+ service.setSize(SAMPLE_SIZE, SAMPLE_SIZE);
+ BufferedImage drawable = service.renderDrawable(resValue);
+ if (drawable != null) {
+ swtImage = SwtUtils.convertToSwt(gc.getDevice(), drawable,
+ true /*transferAlpha*/, -1);
+ cache.put(value, swtImage);
+ }
+ }
+ } else if (value.endsWith(DOT_PNG)) {
+ // TODO: 9-patch handling?
+ //if (text.endsWith(DOT_9PNG)) {
+ // // 9-patch image: How do we paint this?
+ // URL url = new File(text).toURI().toURL();
+ // NinePatch ninepatch = NinePatch.load(url, false /* ?? */);
+ // BufferedImage image = ninepatch.getImage();
+ //}
+ Map<String, Image> cache = getImageCache(property);
+ swtImage = cache.get(value);
+ if (swtImage == null) {
+ File file = new File(value);
+ if (file.exists()) {
+ try {
+ BufferedImage awtImage = ImageIO.read(file);
+ if (awtImage != null && awtImage.getWidth() > 0
+ && awtImage.getHeight() > 0) {
+ awtImage = ImageUtils.cropBlank(awtImage, null);
+ if (awtImage != null) {
+ // Scale image
+ int imageWidth = awtImage.getWidth();
+ int imageHeight = awtImage.getHeight();
+ int maxWidth = 3 * height;
+
+ if (imageWidth > maxWidth || imageHeight > height) {
+ double scale = height / (double) imageHeight;
+ int scaledWidth = (int) (imageWidth * scale);
+ if (scaledWidth > maxWidth) {
+ scale = maxWidth / (double) imageWidth;
+ }
+ awtImage = ImageUtils.scale(awtImage, scale,
+ scale);
+ }
+ swtImage = SwtUtils.convertToSwt(gc.getDevice(),
+ awtImage, true /*transferAlpha*/, -1);
+ }
+ }
+ } catch (IOException e) {
+ AdtPlugin.log(e, value);
+ }
+ }
+ cache.put(value, swtImage);
+ }
+
+ } else if (value != null) {
+ // It's a normal string: if different from the text, paint
+ // it in parentheses, e.g.
+ // @string/foo: Foo Bar (probably cropped)
+ if (!value.equals(text) && !value.equals("@null")) { //$NON-NLS-1$
+ resolvedText = value;
+ }
+ }
+
+ if (swtImage != null) {
+ // Make a square the size of the height
+ ImageData imageData = swtImage.getImageData();
+ int imageWidth = imageData.width;
+ int imageHeight = imageData.height;
+ if (imageWidth > 0 && imageHeight > 0) {
+ gc.drawImage(swtImage, x, y + (height - imageHeight) / 2);
+ int delta = imageWidth + SAMPLE_MARGIN;
+ x += delta;
+ width -= delta;
+ }
+ }
+ }
+ }
+
+ DrawUtils.drawStringCV(gc, text, x, y, width, height);
+
+ if (resolvedText != null && resolvedText.length() > 0) {
+ Point size = gc.stringExtent(text);
+ x += size.x;
+ width -= size.x;
+
+ x += SAMPLE_MARGIN;
+ width -= SAMPLE_MARGIN;
+
+ if (width > 0) {
+ Color oldForeground = gc.getForeground();
+ try {
+ gc.setForeground(PropertyTable.COLOR_PROPERTY_FG_DEFAULT);
+ DrawUtils.drawStringCV(gc, '(' + resolvedText + ')', x, y, width, height);
+ } finally {
+ gc.setForeground(oldForeground);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected boolean setEditorText(Property property, String text) throws Exception {
+ property.setValue(text);
+ return true;
+ }
+
+ private void openDialog(PropertyTable propertyTable, Property property) throws Exception {
+ XmlProperty xmlProperty = (XmlProperty) property;
+ IAttributeInfo attributeInfo = xmlProperty.getDescriptor().getAttributeInfo();
+
+ boolean isId = xmlProperty.getDescriptor().getXmlLocalName().equals(ATTR_ID);
+ if (isId) {
+ // When editing the id attribute, don't offer a resource chooser: usually
+ // you want to enter a *new* id here
+ attributeInfo = null;
+ }
+
+ boolean referenceAllowed = false;
+ if (attributeInfo != null) {
+ EnumSet<Format> formats = attributeInfo.getFormats();
+ ResourceType type = null;
+ List<ResourceType> types = null;
+ if (formats.contains(Format.FLAG)) {
+ FlagXmlPropertyDialog dialog =
+ new FlagXmlPropertyDialog(propertyTable.getShell(),
+ "Select Flag Values", false /* radio */,
+ attributeInfo.getFlagValues(), xmlProperty);
+
+ dialog.open();
+ return;
+
+ } else if (formats.contains(Format.ENUM)) {
+ FlagXmlPropertyDialog dialog =
+ new FlagXmlPropertyDialog(propertyTable.getShell(),
+ "Select Enum Value", true /* radio */,
+ attributeInfo.getEnumValues(), xmlProperty);
+ dialog.open();
+ return;
+ } else {
+ for (Format format : formats) {
+ ResourceType t = format.getResourceType();
+ if (t != null) {
+ if (type != null) {
+ if (types == null) {
+ types = new ArrayList<ResourceType>();
+ types.add(type);
+ }
+ types.add(t);
+ }
+ type = t;
+ } else if (format == Format.REFERENCE) {
+ referenceAllowed = true;
+ }
+ }
+ }
+ if (types != null || referenceAllowed) {
+ // Multiple resource types (such as string *and* boolean):
+ // just use a reference chooser
+ GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor();
+ LayoutEditorDelegate delegate = graphicalEditor.getEditorDelegate();
+ IProject project = delegate.getEditor().getProject();
+ if (project != null) {
+ // get the resource repository for this project and the system resources.
+ ResourceRepository projectRepository =
+ ResourceManager.getInstance().getProjectResources(project);
+ Shell shell = AdtPlugin.getDisplay().getActiveShell();
+ ReferenceChooserDialog dlg = new ReferenceChooserDialog(
+ project,
+ projectRepository,
+ shell);
+ dlg.setPreviewHelper(new ResourcePreviewHelper(dlg, graphicalEditor));
+
+ String currentValue = (String) property.getValue();
+ dlg.setCurrentResource(currentValue);
+
+ if (dlg.open() == Window.OK) {
+ String resource = dlg.getCurrentResource();
+ if (resource != null) {
+ // Returns null for cancel, "" for clear and otherwise a new value
+ if (resource.length() > 0) {
+ property.setValue(resource);
+ } else {
+ property.setValue(null);
+ }
+ }
+ }
+
+ return;
+ }
+
+ } else if (type != null) {
+ // Single resource type: use a resource chooser
+ GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor();
+ String currentValue = (String) property.getValue();
+ // TODO: Add validator factory?
+ String resource = ResourceChooser.chooseResource(graphicalEditor,
+ type, currentValue, null /* validator */);
+ // Returns null for cancel, "" for clear and otherwise a new value
+ if (resource != null) {
+ if (resource.length() > 0) {
+ property.setValue(resource);
+ } else {
+ property.setValue(null);
+ }
+ }
+
+ return;
+ }
+ }
+
+ // Fallback: Just use a plain string editor
+ StringXmlPropertyDialog dialog =
+ new StringXmlPropertyDialog(propertyTable.getShell(), property);
+ if (dialog.open() == Window.OK) {
+ // TODO: Do I need to activate?
+ }
+ }
+
+ /** Qualified name for the per-project persistent property include-map */
+ private final static QualifiedName CACHE_NAME = new QualifiedName(AdtPlugin.PLUGIN_ID,
+ "property-images");//$NON-NLS-1$
+
+ @NonNull
+ private static Map<String, Image> getImageCache(@NonNull Property property) {
+ XmlProperty xmlProperty = (XmlProperty) property;
+ IProject project = xmlProperty.getGraphicalEditor().getProject();
+ try {
+ Map<String, Image> cache = (Map<String, Image>) project.getSessionProperty(CACHE_NAME);
+ if (cache == null) {
+ cache = Maps.newHashMap();
+ project.setSessionProperty(CACHE_NAME, cache);
+ }
+
+ return cache;
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ return Maps.newHashMap();
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
index e5fe678..219754b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
@@ -130,7 +130,7 @@ public class UiElementNode implements IPropertySource {
private Map<String, AttributeDescriptor> mCachedHiddenAttributes;
/** An optional list of {@link IUiUpdateListener}. Most element nodes will not have any
* listeners attached, so the list is only created on demand and can be null. */
- private ArrayList<IUiUpdateListener> mUiUpdateListeners;
+ private List<IUiUpdateListener> mUiUpdateListeners;
/** A provider that knows how to create {@link ElementDescriptor} from unmapped XML names.
* The default is to have one that creates new {@link ElementDescriptor}. */
private IUnknownDescriptorProvider mUnknownDescProvider;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java
index 055d432..e2b2094 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java
@@ -309,8 +309,11 @@ public class UiResourceAttributeNode extends UiTextAttributeNode {
* <p>
* For example, if you are editing a style attribute, it's likely that among the
* resource values you would rather see @style or @android than @string.
+ * @param descriptor the descriptor that the resource values are being completed for,
+ * used to prioritize some of the resource types
+ * @param choices the set of string resource values
*/
- private static void sortAttributeChoices(AttributeDescriptor descriptor,
+ public static void sortAttributeChoices(AttributeDescriptor descriptor,
List<String> choices) {
final IAttributeInfo attributeInfo = descriptor.getAttributeInfo();
Collections.sort(choices, new Comparator<String>() {
@@ -319,7 +322,7 @@ public class UiResourceAttributeNode extends UiTextAttributeNode {
int compare = score(attributeInfo, s1) - score(attributeInfo, s2);
if (compare == 0) {
// Sort alphabetically as a fallback
- compare = s1.compareTo(s2);
+ compare = s1.compareToIgnoreCase(s2);
}
return compare;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java
index 54b5716..4906730 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java
@@ -135,7 +135,9 @@ public class ReferenceChooserDialog extends SelectionStatusDialog {
createFilteredTree(top);
// setup the initial selection
- setupInitialSelection();
+ if (mCurrentResource != null) {
+ setupInitialSelection();
+ }
// create the "New Resource" button
createNewResButtons(top);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java
index 263cc2d..202e1cf 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java
@@ -20,6 +20,7 @@ package com.android.ide.eclipse.adt.internal.ui;
import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_RESOURCE_REF;
import static com.android.ide.common.resources.ResourceResolver.PREFIX_RESOURCE_REF;
+import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceItem;
import com.android.ide.common.resources.ResourceRepository;
@@ -27,6 +28,7 @@ import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.assetstudio.OpenCreateAssetSetWizardAction;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
@@ -770,4 +772,24 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
updateStatus(status);
}
}
+
+ /**
+ * Open the resource chooser for the given type, associated with the given
+ * editor
+ *
+ * @param graphicalEditor the editor associated with the resource to be
+ * chosen (used to find the associated Android target to be used
+ * for framework resources etc)
+ * @param type the resource type to be chosen
+ * @param currentValue the current value, or null
+ * @param validator a validator to be used, or null
+ * @return the chosen resource, null if cancelled and "" if value should be
+ * cleared
+ */
+ public static String chooseResource(
+ @NonNull GraphicalEditorPart graphicalEditor,
+ @NonNull ResourceType type,
+ String currentValue, IInputValidator validator) {
+ return ResourceChooser.chooseResource(graphicalEditor, type, currentValue, validator);
+ }
}