aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties')
-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/FlagXmlPropertyDialog.java217
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java750
-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.java403
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyValueCompleter.java41
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java182
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/StringXmlPropertyDialog.java47
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java186
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlProperty.java278
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyComposite.java121
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java548
13 files changed, 3297 insertions, 0 deletions
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 000000000..d6ff4d51d
--- /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.SdkConstants.VALUE_FALSE;
+import static com.android.SdkConstants.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 000000000..f1a3f2aaa
--- /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/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 000000000..5e1e7029f
--- /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,217 @@
+/*
+ * 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));
+
+ Composite workaround = PropertyFactory.addWorkaround(container);
+ if (workaround != null) {
+ workaround.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 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 000000000..2b8cfbf43
--- /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,750 @@
+/*
+ * 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.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN;
+import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_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.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.browser.IWebBrowser;
+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.editor.presentation.ButtonPropertyEditorPresentation;
+
+import java.net.URL;
+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;
+import java.util.WeakHashMap;
+
+/**
+ * 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)
+ */
+public class PropertyFactory {
+ /** Disable cache during development only */
+ @SuppressWarnings("unused")
+ 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 Map<UiViewElementNode, Property[]> mCache =
+ new WeakHashMap<UiViewElementNode, Property[]>();
+ 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;
+
+ public PropertyFactory(GraphicalEditorPart graphicalEditorPart) {
+ mGraphicalEditorPart = graphicalEditorPart;
+ }
+
+ /**
+ * 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);
+ 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) {
+ 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)) {
+ // We deliberately don't use EnumXmlPropertyEditor.INSTANCE here,
+ // since some attributes (such as layout_width) can have not just one
+ // of the enum values but custom values such as "42dp" as well. And
+ // furthermore, we don't even bother limiting this to formats.size()==1,
+ // since the editing experience with the enum property editor is
+ // more limited than the text editor plus enum completer anyway
+ // (for example, you can't type to filter the values, and clearing
+ // the value is harder.)
+ }
+ }
+
+ 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();
+
+ if (properties.isEmpty()) {
+ return properties;
+ }
+
+ 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_RESOURCE_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_RESOURCE_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;
+ }
+
+ @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;
+ }
+
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=388574
+ public static Composite addWorkaround(Composite parent) {
+ if (ButtonPropertyEditorPresentation.isInWorkaround) {
+ Composite top = new Composite(parent, SWT.NONE);
+ top.setLayout(new GridLayout(1, false));
+ Label label = new Label(top, SWT.WRAP);
+ label.setText(
+ "This dialog is shown instead of an inline text editor as a\n" +
+ "workaround for an Eclipse bug specific to OSX Mountain Lion.\n" +
+ "It should be fixed in Eclipse 4.3.");
+ label.setForeground(top.getDisplay().getSystemColor(SWT.COLOR_RED));
+ GridData data = new GridData();
+ data.grabExcessVerticalSpace = false;
+ data.grabExcessHorizontalSpace = false;
+ data.horizontalAlignment = GridData.FILL;
+ data.verticalAlignment = GridData.BEGINNING;
+ label.setLayoutData(data);
+
+ Link link = new Link(top, SWT.NO_FOCUS);
+ link.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+ link.setText("<a>https://bugs.eclipse.org/bugs/show_bug.cgi?id=388574</a>");
+ link.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent event) {
+ try {
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ IWebBrowser browser = workbench.getBrowserSupport().getExternalBrowser();
+ browser.openURL(new URL(event.text));
+ } catch (Exception e) {
+ String message = String.format(
+ "Could not open browser. Vist\n%1$s\ninstead.",
+ event.text);
+ MessageDialog.openError(((Link)event.getSource()).getShell(),
+ "Browser Error", message);
+ }
+ }
+ });
+
+ return top;
+ }
+
+ return null;
+ }
+}
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 000000000..b230aa99d
--- /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.SdkConstants.ATTR_CONTENT_DESCRIPTION;
+import static com.android.SdkConstants.ATTR_HINT;
+import static com.android.SdkConstants.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 000000000..58fddc0ee
--- /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,403 @@
+/*
+ * 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.editor.structure.IPage;
+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, IPage {
+ private PropertyTable mPropertyTable;
+ private final GraphicalEditorPart mEditor;
+ 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;
+ }
+
+ private PropertyFactory getPropertyFactory() {
+ return mEditor.getPropertyFactory();
+ }
+
+ @Override
+ public void createControl(Composite parent) {
+ assert parent != null;
+ 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",
+ "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;
+
+ // We get a lot of repeated selection requests for the same selection
+ // as before, so try to eliminate these
+ if (mSelection != null) {
+ if (mSelection.isEmpty()) {
+ if (treeSelection.isEmpty()) {
+ return;
+ }
+ } else {
+ int selectionCount = treeSelection.size();
+ if (selectionCount == mSelection.size()) {
+ boolean same = true;
+ Iterator<?> iterator = treeSelection.iterator();
+ for (int i = 0, n = selectionCount; i < n && iterator.hasNext(); i++) {
+ Object next = iterator.next();
+ if (next instanceof CanvasViewInfo) {
+ CanvasViewInfo info = (CanvasViewInfo) next;
+ if (info != mSelection.get(i)) {
+ same = false;
+ break;
+ }
+ } else {
+ same = false;
+ break;
+ }
+ }
+ if (same) {
+ return;
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ @Override
+ public void setToolBar(IToolBarManager toolBarManager) {
+ makeContributions(null, toolBarManager, null);
+ toolBarManager.update(false);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyValueCompleter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyValueCompleter.java
new file mode 100644
index 000000000..f2bf07312
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyValueCompleter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+
+class PropertyValueCompleter extends ValueCompleter {
+ private final XmlProperty mProperty;
+
+ PropertyValueCompleter(XmlProperty property) {
+ mProperty = property;
+ }
+
+ @Override
+ @Nullable
+ protected CommonXmlEditor getEditor() {
+ return mProperty.getXmlEditor();
+ }
+
+ @Override
+ @NonNull
+ protected AttributeDescriptor getDescriptor() {
+ return mProperty.getDescriptor();
+ }
+}
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 000000000..081ec8069
--- /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,182 @@
+/*
+ * 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.SdkConstants.ANDROID_PKG;
+import static com.android.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+
+import com.android.ide.common.resources.ResourceItem;
+import com.android.ide.common.resources.ResourceRepository;
+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 com.android.utils.SdkUtils;
+
+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, ANDROID_PREFIX, 0,
+ Math.min(prefix.length() - 1, ANDROID_PREFIX.length()))) {
+ AndroidTargetData data = editor.getTargetData();
+ if (data != null) {
+ ResourceRepository repository = data.getFrameworkResources();
+ addMatches(repository, prefix, true /* isSystem */, results);
+ }
+ } else if (prefix.startsWith("?") && //$NON-NLS-1$
+ prefix.regionMatches(true /* ignoreCase */, 0, ANDROID_THEME_PREFIX, 0,
+ Math.min(prefix.length() - 1, ANDROID_THEME_PREFIX.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") && !prefix.startsWith("?and")) { //$NON-NLS-1$ //$NON-NLS-2$
+ 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
+ ? ANDROID_PREFIX.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();
+ if (prefix.length() == 0 || prefix.startsWith(PREFIX_RESOURCE_REF)) {
+ sb.append(PREFIX_RESOURCE_REF);
+ } else {
+ if (type != ResourceType.ATTR) {
+ continue;
+ }
+ sb.append(PREFIX_THEME_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 (SdkUtils.startsWithIgnoreCase(name, namePrefix)) {
+ results.add(base + name);
+ }
+ }
+ }
+ }
+ }
+}
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 000000000..fb7e45902
--- /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,47 @@
+/*
+ * 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.SWT;
+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.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;
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite area = (Composite) super.createDialogArea(parent);
+
+ Composite workaround = PropertyFactory.addWorkaround(area);
+ if (workaround != null) {
+ workaround.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+ }
+
+ return area;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java
new file mode 100644
index 000000000..5559349fc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java
@@ -0,0 +1,186 @@
+/*
+ * 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.SdkConstants.ATTR_TEXT_SIZE;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.SdkConstants.UNIT_DP;
+import static com.android.SdkConstants.UNIT_SP;
+import static com.android.SdkConstants.VALUE_FALSE;
+import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.ide.common.api.IAttributeInfo.Format.BOOLEAN;
+import static com.android.ide.common.api.IAttributeInfo.Format.DIMENSION;
+import static com.android.ide.common.api.IAttributeInfo.Format.ENUM;
+import static com.android.ide.common.api.IAttributeInfo.Format.FLAG;
+import static com.android.ide.common.api.IAttributeInfo.Format.FLOAT;
+import static com.android.ide.common.api.IAttributeInfo.Format.INTEGER;
+import static com.android.ide.common.api.IAttributeInfo.Format.REFERENCE;
+import static com.android.ide.common.api.IAttributeInfo.Format.STRING;
+
+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.internal.editors.common.CommonXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.utils.SdkUtils;
+
+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.EnumSet;
+import java.util.List;
+
+/**
+ * An {@link IContentProposalProvider} which completes possible property values
+ * for Android properties, completing resource strings, flag values, enum
+ * values, as well as dimension units.
+ */
+abstract class ValueCompleter implements IContentProposalProvider {
+ @Nullable
+ protected abstract CommonXmlEditor getEditor();
+
+ @NonNull
+ protected abstract AttributeDescriptor getDescriptor();
+
+ @Override
+ public IContentProposal[] getProposals(String contents, int position) {
+ AttributeDescriptor descriptor = getDescriptor();
+ IAttributeInfo info = descriptor.getAttributeInfo();
+ EnumSet<Format> formats = info.getFormats();
+
+ List<IContentProposal> proposals = new ArrayList<IContentProposal>();
+
+ String prefix = contents; // TODO: Go back to position inside the array?
+
+ // TODO: If the user is typing in a number, or a number plus a prefix of a dimension unit,
+ // then propose that number plus the completed dimension unit (using sp for text, dp
+ // for other properties and maybe both if I'm not sure)
+ if (formats.contains(STRING)
+ && !contents.isEmpty()
+ && (formats.size() > 1 && formats.contains(REFERENCE) ||
+ formats.size() > 2)
+ && !contents.startsWith(PREFIX_RESOURCE_REF)
+ && !contents.startsWith(PREFIX_THEME_REF)) {
+ proposals.add(new ContentProposal(contents));
+ }
+
+ if (!contents.isEmpty() && Character.isDigit(contents.charAt(0))
+ && (formats.contains(DIMENSION)
+ || formats.contains(INTEGER)
+ || formats.contains(FLOAT))) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0, n = contents.length(); i < n; i++) {
+ char c = contents.charAt(i);
+ if (Character.isDigit(c)) {
+ sb.append(c);
+ } else {
+ break;
+ }
+ }
+
+ String number = sb.toString();
+ if (formats.contains(Format.DIMENSION)) {
+ if (descriptor.getXmlLocalName().equals(ATTR_TEXT_SIZE)) {
+ proposals.add(new ContentProposal(number + UNIT_SP));
+ }
+ proposals.add(new ContentProposal(number + UNIT_DP));
+ } else if (formats.contains(Format.INTEGER)) {
+ proposals.add(new ContentProposal(number));
+ }
+ // Perhaps offer other units too -- see AndroidContentAssist.sDimensionUnits
+ }
+
+ if (formats.contains(REFERENCE) || contents.startsWith(PREFIX_RESOURCE_REF)
+ || contents.startsWith(PREFIX_THEME_REF)) {
+ CommonXmlEditor editor = getEditor();
+ if (editor != null) {
+ String[] matches = ResourceValueCompleter.computeResourceStringMatches(
+ editor,
+ descriptor, contents.substring(0, position));
+ for (String match : matches) {
+ proposals.add(new ContentProposal(match));
+ }
+ }
+ }
+
+ if (formats.contains(FLAG)) {
+ String[] values = info.getFlagValues();
+ if (values != null) {
+ // Flag completion
+ 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 : values) {
+ if (prefix.equals(value)) {
+ exactMatch = true;
+ proposals.add(new ContentProposal(contents));
+
+ break;
+ }
+ }
+
+ if (exactMatch) {
+ prepend = contents + '|';
+ prefix = "";
+ }
+
+ for (String value : values) {
+ if (SdkUtils.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));
+ }
+ }
+ }
+ } else if (formats.contains(ENUM)) {
+ String[] values = info.getEnumValues();
+ if (values != null) {
+ for (String value : values) {
+ if (SdkUtils.startsWithIgnoreCase(value, prefix)) {
+ proposals.add(new ContentProposal(value));
+ }
+ }
+
+ for (String value : values) {
+ if (!SdkUtils.startsWithIgnoreCase(value, prefix)) {
+ proposals.add(new ContentProposal(value));
+ }
+ }
+ }
+ } else if (formats.contains(BOOLEAN)) {
+ proposals.add(new ContentProposal(VALUE_TRUE));
+ proposals.add(new ContentProposal(VALUE_FALSE));
+ }
+
+ return proposals.toArray(new IContentProposal[proposals.size()]);
+ }
+}
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 000000000..a320b682d
--- /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,278 @@
+/*
+ * 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.SdkConstants.ATTR_LAYOUT_MARGIN;
+import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.IAttributeInfo;
+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.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;
+ }
+
+ @NonNull
+ public PropertyFactory getFactory() {
+ return mFactory;
+ }
+
+ @NonNull
+ public UiViewElementNode getNode() {
+ return mNode;
+ }
+
+ @NonNull
+ public AttributeDescriptor getDescriptor() {
+ return mDescriptor;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mDescriptor.getXmlLocalName();
+ }
+
+ @Override
+ @NonNull
+ public String getTitle() {
+ String name = mDescriptor.getXmlLocalName();
+ int nameLength = name.length();
+
+ if (name.startsWith(ATTR_LAYOUT_RESOURCE_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_RESOURCE_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) {
+ return adapter.cast(new PropertyValueCompleter(this));
+ }
+ // 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();
+ if (element == null) {
+ return false;
+ }
+ 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;
+ }
+
+ @Nullable
+ public String getStringValue() {
+ Element element = (Element) mNode.getXmlNode();
+ if (element == null) {
+ return null;
+ }
+ 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) {
+ GraphicalEditorPart graphicalEditor = getGraphicalEditor();
+ if (graphicalEditor == null) {
+ return null;
+ }
+ ViewHierarchy views = graphicalEditor.getCanvasControl().getViewHierarchy();
+ Map<String, String> defaultProperties = views.getDefaultProperties(viewObject);
+ if (defaultProperties != null) {
+ return defaultProperties.get(name);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ @Nullable
+ 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
+ @NonNull
+ 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;
+ }
+
+ @Nullable
+ public Property getParent() {
+ return mParent;
+ }
+
+ public void setParent(@Nullable 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 000000000..af9e13b3e
--- /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,121 @@
+/*
+ * 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.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
+ @NonNull
+ 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);
+ }
+ }
+
+ @NonNull
+ 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 000000000..87fb0e6ed
--- /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,548 @@
+/*
+ * 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.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.DOT_PNG;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.ide.common.layout.BaseViewRule.stripIdPrefix;
+
+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.layout.BaseViewRule;
+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.common.CommonXmlEditor;
+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.LayoutCanvas;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionManager;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceWizard;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResult;
+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.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialogWithToggle;
+import org.eclipse.jface.preference.IPreferenceStore;
+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;
+ GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor();
+ if (graphicalEditor != null) {
+ ResourceResolver resolver = graphicalEditor.getResourceResolver();
+ boolean isFramework = text.startsWith(ANDROID_PREFIX)
+ || text.startsWith(ANDROID_THEME_PREFIX);
+ resValue = resolver.findResValue(text, isFramework);
+ while (resValue != null && resValue.getValue() != null) {
+ String value = resValue.getValue();
+ if (value.startsWith(PREFIX_RESOURCE_REF)
+ || value.startsWith(PREFIX_THEME_REF)) {
+ // TODO: do I have to strip off the @ too?
+ isFramework = isFramework
+ || value.startsWith(ANDROID_PREFIX)
+ || value.startsWith(ANDROID_THEME_PREFIX);
+ 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;
+ GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor();
+ if (graphicalEditor != null) {
+ ResourceResolver resolver = graphicalEditor.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.setOverrideRenderSize(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 {
+ Object oldValue = property.getValue();
+ String old = oldValue != null ? oldValue.toString() : null;
+
+ // If users enters a new id without specifying the @id/@+id prefix, insert it
+ boolean isId = isIdProperty(property);
+ if (isId && !text.startsWith(PREFIX_RESOURCE_REF)) {
+ text = NEW_ID_PREFIX + text;
+ }
+
+ // Handle id refactoring: if you change an id, may want to update references too.
+ // Ask user.
+ if (isId && property instanceof XmlProperty
+ && old != null && !old.isEmpty()
+ && text != null && !text.isEmpty()
+ && !text.equals(old)) {
+ XmlProperty xmlProperty = (XmlProperty) property;
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ String refactorPref = store.getString(AdtPrefs.PREFS_REFACTOR_IDS);
+ boolean performRefactor = false;
+ Shell shell = AdtPlugin.getShell();
+ if (refactorPref == null
+ || refactorPref.isEmpty()
+ || refactorPref.equals(MessageDialogWithToggle.PROMPT)) {
+ MessageDialogWithToggle dialog =
+ MessageDialogWithToggle.openYesNoCancelQuestion(
+ shell,
+ "Update References?",
+ "Update all references as well? " +
+ "This will update all XML references and Java R field references.",
+ "Do not show again",
+ false,
+ store,
+ AdtPrefs.PREFS_REFACTOR_IDS);
+ switch (dialog.getReturnCode()) {
+ case IDialogConstants.CANCEL_ID:
+ return false;
+ case IDialogConstants.YES_ID:
+ performRefactor = true;
+ break;
+ case IDialogConstants.NO_ID:
+ performRefactor = false;
+ break;
+ }
+ } else {
+ performRefactor = refactorPref.equals(MessageDialogWithToggle.ALWAYS);
+ }
+ if (performRefactor) {
+ CommonXmlEditor xmlEditor = xmlProperty.getXmlEditor();
+ if (xmlEditor != null) {
+ IProject project = xmlEditor.getProject();
+ if (project != null && shell != null) {
+ RenameResourceWizard.renameResource(shell, project,
+ ResourceType.ID, stripIdPrefix(old), stripIdPrefix(text), false);
+ }
+ }
+ }
+ }
+
+ property.setValue(text);
+
+ return true;
+ }
+
+ private static boolean isIdProperty(Property property) {
+ XmlProperty xmlProperty = (XmlProperty) property;
+ return xmlProperty.getDescriptor().getXmlLocalName().equals(ATTR_ID);
+ }
+
+ private void openDialog(PropertyTable propertyTable, Property property) throws Exception {
+ XmlProperty xmlProperty = (XmlProperty) property;
+ IAttributeInfo attributeInfo = xmlProperty.getDescriptor().getAttributeInfo();
+
+ if (isIdProperty(property)) {
+ Object value = xmlProperty.getValue();
+ if (value != null && !value.toString().isEmpty()) {
+ GraphicalEditorPart editor = xmlProperty.getGraphicalEditor();
+ if (editor != null) {
+ LayoutCanvas canvas = editor.getCanvasControl();
+ SelectionManager manager = canvas.getSelectionManager();
+
+ NodeProxy primary = canvas.getNodeFactory().create(xmlProperty.getNode());
+ if (primary != null) {
+ RenameResult result = manager.performRename(primary, null);
+ if (result.isCanceled()) {
+ return;
+ } else if (!result.isUnavailable()) {
+ String name = result.getName();
+ String id = NEW_ID_PREFIX + BaseViewRule.stripIdPrefix(name);
+ xmlProperty.setValue(id);
+ return;
+ }
+ }
+ }
+ }
+
+ // 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)) {
+ String[] flagValues = attributeInfo.getFlagValues();
+ if (flagValues != null) {
+ FlagXmlPropertyDialog dialog =
+ new FlagXmlPropertyDialog(propertyTable.getShell(),
+ "Select Flag Values", false /* radio */,
+ flagValues, xmlProperty);
+
+ dialog.open();
+ return;
+ }
+ } else if (formats.contains(Format.ENUM)) {
+ String[] enumValues = attributeInfo.getEnumValues();
+ if (enumValues != null) {
+ FlagXmlPropertyDialog dialog =
+ new FlagXmlPropertyDialog(propertyTable.getShell(),
+ "Select Enum Value", true /* radio */,
+ enumValues, 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();
+ if (graphicalEditor != null) {
+ 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.getShell();
+ 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();
+ if (graphicalEditor != null) {
+ 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;
+ GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor();
+ IProject project = graphicalEditor.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();
+ }
+ }
+}