aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java856
1 files changed, 856 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java
new file mode 100644
index 000000000..5f2659ef2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java
@@ -0,0 +1,856 @@
+/*
+ * Copyright (C) 2011 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.gre;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.FQCN_BUTTON;
+import static com.android.SdkConstants.FQCN_SPINNER;
+import static com.android.SdkConstants.FQCN_TOGGLE_BUTTON;
+import static com.android.SdkConstants.ID_PREFIX;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.VIEW_FRAGMENT;
+import static com.android.SdkConstants.VIEW_INCLUDE;
+
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.api.IViewMetadata.FillPreference;
+import com.android.ide.common.api.Margins;
+import com.android.ide.common.api.ResizePolicy;
+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.descriptors.LayoutDescriptors;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.resources.Density;
+import com.android.utils.Pair;
+import com.google.common.base.Splitter;
+import com.google.common.io.Closeables;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * The {@link ViewMetadataRepository} contains additional metadata for Android view
+ * classes
+ */
+public class ViewMetadataRepository {
+ private static final String PREVIEW_CONFIG_FILENAME = "rendering-configs.xml"; //$NON-NLS-1$
+ private static final String METADATA_FILENAME = "extra-view-metadata.xml"; //$NON-NLS-1$
+
+ /** Singleton instance */
+ private static ViewMetadataRepository sInstance = new ViewMetadataRepository();
+
+ /**
+ * Returns the singleton instance
+ *
+ * @return the {@link ViewMetadataRepository}
+ */
+ public static ViewMetadataRepository get() {
+ return sInstance;
+ }
+
+ /**
+ * Ever increasing counter used to assign natural ordering numbers to views and
+ * categories
+ */
+ private static int sNextOrdinal = 0;
+
+ /**
+ * List of categories (which contain views); constructed lazily so use
+ * {@link #getCategories()}
+ */
+ private List<CategoryData> mCategories;
+
+ /**
+ * Map from class names to view data objects; constructed lazily so use
+ * {@link #getClassToView}
+ */
+ private Map<String, ViewData> mClassToView;
+
+ /** Hidden constructor: Create via factory {@link #get()} instead */
+ private ViewMetadataRepository() {
+ }
+
+ /** Returns a map from class fully qualified names to {@link ViewData} objects */
+ private Map<String, ViewData> getClassToView() {
+ if (mClassToView == null) {
+ int initialSize = 75;
+ mClassToView = new HashMap<String, ViewData>(initialSize);
+ List<CategoryData> categories = getCategories();
+ for (CategoryData category : categories) {
+ for (ViewData view : category) {
+ mClassToView.put(view.getFcqn(), view);
+ }
+ }
+ assert mClassToView.size() <= initialSize;
+ }
+
+ return mClassToView;
+ }
+
+ /**
+ * Returns an XML document containing rendering configurations for the various Android
+ * views. The FQN of each view can be obtained via the
+ * {@link #getFullClassName(Element)} method
+ *
+ * @return an XML document containing rendering elements
+ */
+ public Document getRenderingConfigDoc() {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
+ InputStream paletteStream = clz.getResourceAsStream(PREVIEW_CONFIG_FILENAME);
+ InputSource is = new InputSource(paletteStream);
+ try {
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setIgnoringComments(true);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ return builder.parse(is);
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Parsing palette file failed");
+ return null;
+ } finally {
+ Closeables.closeQuietly(paletteStream);
+ }
+ }
+
+ /**
+ * Returns a fully qualified class name for an element in the rendering document
+ * returned by {@link #getRenderingConfigDoc()}
+ *
+ * @param element the element to look up the fqcn for
+ * @return the fqcn of the view the element represents a preview for
+ */
+ public String getFullClassName(Element element) {
+ // We don't use the element tag name, because in some cases we have
+ // an outer element to render some interesting inner element, such as a tab widget
+ // (which must be rendered inside a tab host).
+ //
+ // Therefore, we instead use the convention that the id is the fully qualified
+ // class name, with .'s replaced with _'s.
+
+ // Special case: for tab host we aren't allowed to mess with the id
+ String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
+
+ if ("@android:id/tabhost".equals(id)) {
+ // Special case to distinguish TabHost and TabWidget
+ NodeList children = element.getChildNodes();
+ if (children.getLength() > 1 && (children.item(1) instanceof Element)) {
+ Element child = (Element) children.item(1);
+ String childId = child.getAttributeNS(ANDROID_URI, ATTR_ID);
+ if ("@+id/android_widget_TabWidget".equals(childId)) {
+ return "android.widget.TabWidget"; // TODO: Tab widget!
+ }
+ }
+ return "android.widget.TabHost"; // TODO: Tab widget!
+ }
+
+ StringBuilder sb = new StringBuilder();
+ int i = 0;
+ if (id.startsWith(NEW_ID_PREFIX)) {
+ i = NEW_ID_PREFIX.length();
+ } else if (id.startsWith(ID_PREFIX)) {
+ i = ID_PREFIX.length();
+ }
+
+ for (; i < id.length(); i++) {
+ char c = id.charAt(i);
+ if (c == '_') {
+ sb.append('.');
+ } else {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /** Returns an ordered list of categories and views, parsed from a metadata file */
+ @SuppressWarnings("resource") // streams passed to parser InputSource closed by parser
+ private List<CategoryData> getCategories() {
+ if (mCategories == null) {
+ mCategories = new ArrayList<CategoryData>();
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
+ InputStream inputStream = clz.getResourceAsStream(METADATA_FILENAME);
+ InputSource is = new InputSource(new BufferedInputStream(inputStream));
+ try {
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setIgnoringComments(true);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document document = builder.parse(is);
+ Map<String, FillPreference> fillTypes = new HashMap<String, FillPreference>();
+ for (FillPreference pref : FillPreference.values()) {
+ fillTypes.put(pref.toString().toLowerCase(Locale.US), pref);
+ }
+
+ NodeList categoryNodes = document.getDocumentElement().getChildNodes();
+ for (int i = 0, n = categoryNodes.getLength(); i < n; i++) {
+ Node node = categoryNodes.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) node;
+ if (element.getNodeName().equals("category")) { //$NON-NLS-1$
+ String name = element.getAttribute("name"); //$NON-NLS-1$
+ CategoryData category = new CategoryData(name);
+ NodeList children = element.getChildNodes();
+ for (int j = 0, m = children.getLength(); j < m; j++) {
+ Node childNode = children.item(j);
+ if (childNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element child = (Element) childNode;
+ ViewData view = createViewData(fillTypes, child,
+ null, FillPreference.NONE, RenderMode.NORMAL, null);
+ category.addView(view);
+ }
+ }
+ mCategories.add(category);
+ }
+ }
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Invalid palette metadata"); //$NON-NLS-1$
+ }
+ }
+
+ return mCategories;
+ }
+
+ private ViewData createViewData(Map<String, FillPreference> fillTypes,
+ Element child, String defaultFqcn, FillPreference defaultFill,
+ RenderMode defaultRender, String defaultSize) {
+ String fqcn = child.getAttribute("class"); //$NON-NLS-1$
+ if (fqcn.length() == 0) {
+ fqcn = defaultFqcn;
+ }
+ String fill = child.getAttribute("fill"); //$NON-NLS-1$
+ FillPreference fillPreference = null;
+ if (fill.length() > 0) {
+ fillPreference = fillTypes.get(fill);
+ }
+ if (fillPreference == null) {
+ fillPreference = defaultFill;
+ }
+ String skip = child.getAttribute("skip"); //$NON-NLS-1$
+ RenderMode renderMode = defaultRender;
+ String render = child.getAttribute("render"); //$NON-NLS-1$
+ if (render.length() > 0) {
+ renderMode = RenderMode.get(render);
+ }
+ String displayName = child.getAttribute("name"); //$NON-NLS-1$
+ if (displayName.length() == 0) {
+ displayName = null;
+ }
+
+ String relatedTo = child.getAttribute("relatedTo"); //$NON-NLS-1$
+ String topAttrs = child.getAttribute("topAttrs"); //$NON-NLS-1$
+ String resize = child.getAttribute("resize"); //$NON-NLS-1$
+ ViewData view = new ViewData(fqcn, displayName, fillPreference,
+ skip.length() == 0 ? false : Boolean.valueOf(skip),
+ renderMode, relatedTo, resize, topAttrs);
+
+ String init = child.getAttribute("init"); //$NON-NLS-1$
+ String icon = child.getAttribute("icon"); //$NON-NLS-1$
+
+ view.setInitString(init);
+ if (icon.length() > 0) {
+ view.setIconName(icon);
+ }
+
+ // Nested variations?
+ if (child.hasChildNodes()) {
+ // Palette variations
+ NodeList childNodes = child.getChildNodes();
+ for (int k = 0, kl = childNodes.getLength(); k < kl; k++) {
+ Node variationNode = childNodes.item(k);
+ if (variationNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element variation = (Element) variationNode;
+ ViewData variationView = createViewData(fillTypes, variation,
+ fqcn, fillPreference, renderMode, resize);
+ view.addVariation(variationView);
+ }
+ }
+ }
+
+ return view;
+ }
+
+ /**
+ * Computes the palette entries for the given {@link AndroidTargetData}, looking up the
+ * available node descriptors, categorizing and sorting them.
+ *
+ * @param targetData the target data for which to compute palette entries
+ * @param alphabetical if true, sort all items in alphabetical order
+ * @param createCategories if true, organize the items into categories
+ * @return a list of pairs where each pair contains of the category label and an
+ * ordered list of elements to be included in that category
+ */
+ public List<Pair<String, List<ViewElementDescriptor>>> getPaletteEntries(
+ AndroidTargetData targetData, boolean alphabetical, boolean createCategories) {
+ List<Pair<String, List<ViewElementDescriptor>>> result =
+ new ArrayList<Pair<String, List<ViewElementDescriptor>>>();
+
+ List<List<ViewElementDescriptor>> lists = new ArrayList<List<ViewElementDescriptor>>(2);
+ LayoutDescriptors layoutDescriptors = targetData.getLayoutDescriptors();
+ lists.add(layoutDescriptors.getViewDescriptors());
+ lists.add(layoutDescriptors.getLayoutDescriptors());
+
+ // First record map of FQCN to ViewElementDescriptor such that we can quickly
+ // determine if a particular palette entry is available
+ Map<String, ViewElementDescriptor> fqcnToDescriptor =
+ new HashMap<String, ViewElementDescriptor>();
+ for (List<ViewElementDescriptor> list : lists) {
+ for (ViewElementDescriptor view : list) {
+ String fqcn = view.getFullClassName();
+ if (fqcn == null) {
+ // <view> and <merge> tags etc
+ fqcn = view.getUiName();
+ }
+ fqcnToDescriptor.put(fqcn, view);
+ }
+ }
+
+ Set<ViewElementDescriptor> remaining = new HashSet<ViewElementDescriptor>(
+ layoutDescriptors.getViewDescriptors().size()
+ + layoutDescriptors.getLayoutDescriptors().size());
+ remaining.addAll(layoutDescriptors.getViewDescriptors());
+ remaining.addAll(layoutDescriptors.getLayoutDescriptors());
+
+ // Now iterate in palette metadata order over the items in the palette and include
+ // any that also appear as a descriptor
+ List<ViewElementDescriptor> categoryItems = new ArrayList<ViewElementDescriptor>();
+ for (CategoryData category : getCategories()) {
+ if (createCategories) {
+ categoryItems = new ArrayList<ViewElementDescriptor>();
+ }
+ for (ViewData view : category) {
+ String fqcn = view.getFcqn();
+ ViewElementDescriptor descriptor = fqcnToDescriptor.get(fqcn);
+ if (descriptor != null) {
+ remaining.remove(descriptor);
+ if (view.getSkip()) {
+ continue;
+ }
+
+ if (view.getDisplayName() != null || view.getInitString().length() > 0) {
+ categoryItems.add(new PaletteMetadataDescriptor(descriptor,
+ view.getDisplayName(), view.getInitString(), view.getIconName()));
+ } else {
+ categoryItems.add(descriptor);
+ }
+
+ if (view.hasVariations()) {
+ for (ViewData variation : view.getVariations()) {
+ String init = variation.getInitString();
+ String icon = variation.getIconName();
+ ViewElementDescriptor desc = new PaletteMetadataDescriptor(descriptor,
+ variation.getDisplayName(), init, icon);
+ categoryItems.add(desc);
+ }
+ }
+ }
+ }
+
+ if (createCategories && categoryItems.size() > 0) {
+ if (alphabetical) {
+ Collections.sort(categoryItems);
+ }
+ result.add(Pair.of(category.getName(), categoryItems));
+ }
+ }
+
+ if (remaining.size() > 0) {
+ List<ViewElementDescriptor> otherItems =
+ new ArrayList<ViewElementDescriptor>(remaining);
+ // Always sorted, we don't have a natural order for these unknowns
+ Collections.sort(otherItems);
+ if (createCategories) {
+ result.add(Pair.of("Other", otherItems));
+ } else {
+ categoryItems.addAll(otherItems);
+ }
+ }
+
+ if (!createCategories) {
+ if (alphabetical) {
+ Collections.sort(categoryItems);
+ }
+ result.add(Pair.of("Views", categoryItems));
+ }
+
+ return result;
+ }
+
+ @VisibleForTesting
+ Collection<String> getAllFqcns() {
+ return getClassToView().keySet();
+ }
+
+ /**
+ * Metadata holder for a particular category - contains the name of the category, its
+ * ordinal (for natural/logical sorting order) and views contained in the category
+ */
+ private static class CategoryData implements Iterable<ViewData>, Comparable<CategoryData> {
+ /** Category name */
+ private final String mName;
+ /** Views included in this category */
+ private final List<ViewData> mViews = new ArrayList<ViewData>();
+ /** Natural ordering rank */
+ private final int mOrdinal = sNextOrdinal++;
+
+ /** Constructs a new category with the given name */
+ private CategoryData(String name) {
+ super();
+ mName = name;
+ }
+
+ /** Adds a new view into this category */
+ private void addView(ViewData view) {
+ mViews.add(view);
+ }
+
+ private String getName() {
+ return mName;
+ }
+
+ // Implements Iterable<ViewData> such that we can use for-each on the category to
+ // enumerate its views
+ @Override
+ public Iterator<ViewData> iterator() {
+ return mViews.iterator();
+ }
+
+ // Implements Comparable<CategoryData> such that categories can be naturally sorted
+ @Override
+ public int compareTo(CategoryData other) {
+ return mOrdinal - other.mOrdinal;
+ }
+ }
+
+ /** Metadata holder for a view of a given fully qualified class name */
+ private static class ViewData implements Comparable<ViewData> {
+ /** The fully qualified class name of the view */
+ private final String mFqcn;
+ /** Fill preference of the view */
+ private final FillPreference mFillPreference;
+ /** Skip this item in the palette? */
+ private final boolean mSkip;
+ /** Must this item be rendered alone? skipped? etc */
+ private final RenderMode mRenderMode;
+ /** Related views */
+ private final String mRelatedTo;
+ /** The relative rank of the view for natural ordering */
+ private final int mOrdinal = sNextOrdinal++;
+ /** List of optional variations */
+ private List<ViewData> mVariations;
+ /** Display name. Can be null. */
+ private String mDisplayName;
+ /**
+ * Optional initialization string - a comma separate set of name/value pairs to
+ * initialize the element with
+ */
+ private String mInitString;
+ /** The name of an icon (known to the {@link IconFactory} to show for this view */
+ private String mIconName;
+ /** The resize preference of this view */
+ private String mResize;
+ /** The most commonly set attributes of this view */
+ private String mTopAttrs;
+
+ /** Constructs a new view data for the given class */
+ private ViewData(String fqcn, String displayName,
+ FillPreference fillPreference, boolean skip, RenderMode renderMode,
+ String relatedTo, String resize, String topAttrs) {
+ super();
+ mFqcn = fqcn;
+ mDisplayName = displayName;
+ mFillPreference = fillPreference;
+ mSkip = skip;
+ mRenderMode = renderMode;
+ mRelatedTo = relatedTo;
+ mResize = resize;
+ mTopAttrs = topAttrs;
+ }
+
+ /** Returns the {@link FillPreference} for views of this type */
+ private FillPreference getFillPreference() {
+ return mFillPreference;
+ }
+
+ /** Fully qualified class name of views of this type */
+ private String getFcqn() {
+ return mFqcn;
+ }
+
+ private String getDisplayName() {
+ return mDisplayName;
+ }
+
+ private String getResize() {
+ return mResize;
+ }
+
+ // Implements Comparable<ViewData> such that views can be sorted naturally
+ @Override
+ public int compareTo(ViewData other) {
+ return mOrdinal - other.mOrdinal;
+ }
+
+ public RenderMode getRenderMode() {
+ return mRenderMode;
+ }
+
+ public boolean getSkip() {
+ return mSkip;
+ }
+
+ public List<String> getRelatedTo() {
+ if (mRelatedTo == null || mRelatedTo.length() == 0) {
+ return Collections.emptyList();
+ } else {
+ List<String> result = new ArrayList<String>();
+ ViewMetadataRepository repository = ViewMetadataRepository.get();
+ Map<String, ViewData> classToView = repository.getClassToView();
+
+ List<String> fqns = new ArrayList<String>(classToView.keySet());
+ for (String basename : Splitter.on(',').split(mRelatedTo)) {
+ boolean found = false;
+ for (String fqcn : fqns) {
+ String suffix = '.' + basename;
+ if (fqcn.endsWith(suffix)) {
+ result.add(fqcn);
+ found = true;
+ break;
+ }
+ }
+ if (basename.equals(VIEW_FRAGMENT) || basename.equals(VIEW_INCLUDE)) {
+ result.add(basename);
+ } else {
+ assert found : basename;
+ }
+ }
+
+ return result;
+ }
+ }
+
+ public List<String> getTopAttributes() {
+ // "id" is a top attribute for all views, so it is not included in the XML, we just
+ // add it in dynamically here
+ if (mTopAttrs == null || mTopAttrs.length() == 0) {
+ return Collections.singletonList(ATTR_ID);
+ } else {
+ String[] split = mTopAttrs.split(","); //$NON-NLS-1$
+ List<String> topAttributes = new ArrayList<String>(split.length + 1);
+ topAttributes.add(ATTR_ID);
+ for (int i = 0, n = split.length; i < n; i++) {
+ topAttributes.add(split[i]);
+ }
+ return Collections.<String>unmodifiableList(topAttributes);
+ }
+ }
+
+ void addVariation(ViewData variation) {
+ if (mVariations == null) {
+ mVariations = new ArrayList<ViewData>(4);
+ }
+ mVariations.add(variation);
+ }
+
+ List<ViewData> getVariations() {
+ return mVariations;
+ }
+
+ boolean hasVariations() {
+ return mVariations != null && mVariations.size() > 0;
+ }
+
+ private void setInitString(String initString) {
+ this.mInitString = initString;
+ }
+
+ private String getInitString() {
+ return mInitString;
+ }
+
+ private void setIconName(String iconName) {
+ this.mIconName = iconName;
+ }
+
+ private String getIconName() {
+ return mIconName;
+ }
+ }
+
+ /**
+ * Returns the {@link FillPreference} for classes with the given fully qualified class
+ * name
+ *
+ * @param fqcn the fully qualified class name of the view
+ * @return a suitable {@link FillPreference} for the given view type
+ */
+ public FillPreference getFillPreference(String fqcn) {
+ ViewData view = getClassToView().get(fqcn);
+ if (view != null) {
+ return view.getFillPreference();
+ }
+
+ return FillPreference.NONE;
+ }
+
+ /**
+ * Returns the {@link RenderMode} for classes with the given fully qualified class
+ * name
+ *
+ * @param fqcn the fully qualified class name
+ * @return the {@link RenderMode} to use for previews of the given view type
+ */
+ public RenderMode getRenderMode(String fqcn) {
+ ViewData view = getClassToView().get(fqcn);
+ if (view != null) {
+ return view.getRenderMode();
+ }
+
+ return RenderMode.NORMAL;
+ }
+
+ /**
+ * Returns the {@link ResizePolicy} for the given class.
+ *
+ * @param fqcn the fully qualified class name of the target widget
+ * @return the {@link ResizePolicy} for the widget, which will never be null (but may
+ * be the default of {@link ResizePolicy#full()} if no metadata is found for
+ * the given widget)
+ */
+ public ResizePolicy getResizePolicy(String fqcn) {
+ ViewData view = getClassToView().get(fqcn);
+ if (view != null) {
+ String resize = view.getResize();
+ if (resize != null && resize.length() > 0) {
+ if ("full".equals(resize)) { //$NON-NLS-1$
+ return ResizePolicy.full();
+ } else if ("none".equals(resize)) { //$NON-NLS-1$
+ return ResizePolicy.none();
+ } else if ("horizontal".equals(resize)) { //$NON-NLS-1$
+ return ResizePolicy.horizontal();
+ } else if ("vertical".equals(resize)) { //$NON-NLS-1$
+ return ResizePolicy.vertical();
+ } else if ("scaled".equals(resize)) { //$NON-NLS-1$
+ return ResizePolicy.scaled();
+ } else {
+ assert false : resize;
+ }
+ }
+ }
+
+ return ResizePolicy.full();
+ }
+
+ /**
+ * Returns true if classes with the given fully qualified class name should be hidden
+ * or skipped from the palette
+ *
+ * @param fqcn the fully qualified class name
+ * @return true if views of the given type should be hidden from the palette
+ */
+ public boolean getSkip(String fqcn) {
+ ViewData view = getClassToView().get(fqcn);
+ if (view != null) {
+ return view.getSkip();
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a list of the top (most commonly set) attributes of the given
+ * view.
+ *
+ * @param fqcn the fully qualified class name
+ * @return a list, never null but possibly empty, of popular attribute names
+ * (not including a namespace prefix)
+ */
+ public List<String> getTopAttributes(String fqcn) {
+ ViewData view = getClassToView().get(fqcn);
+ if (view != null) {
+ return view.getTopAttributes();
+ }
+
+ return Collections.singletonList(ATTR_ID);
+ }
+
+ /**
+ * Returns a set of fully qualified names for views that are closely related to the
+ * given view
+ *
+ * @param fqcn the fully qualified class name
+ * @return a list, never null but possibly empty, of views that are related to the
+ * view of the given type
+ */
+ public List<String> getRelatedTo(String fqcn) {
+ ViewData view = getClassToView().get(fqcn);
+ if (view != null) {
+ return view.getRelatedTo();
+ }
+
+ return Collections.emptyList();
+ }
+
+ /** Render mode for palette preview */
+ public enum RenderMode {
+ /**
+ * Render previews, and it can be rendered as a sibling of many other views in a
+ * big linear layout
+ */
+ NORMAL,
+ /** This view needs to be rendered alone */
+ ALONE,
+ /**
+ * Skip this element; it doesn't work or does not produce any visible artifacts
+ * (such as the basic layouts)
+ */
+ SKIP;
+
+ /**
+ * Returns the {@link RenderMode} for the given render XML attribute
+ * value
+ *
+ * @param render the attribute value in the metadata XML file
+ * @return a corresponding {@link RenderMode}, never null
+ */
+ public static RenderMode get(String render) {
+ if ("alone".equals(render)) { //$NON-NLS-1$
+ return ALONE;
+ } else if ("skip".equals(render)) { //$NON-NLS-1$
+ return SKIP;
+ } else {
+ return NORMAL;
+ }
+ }
+ }
+
+ /**
+ * Are insets supported yet? This flag indicates whether the {@link #getInsets} method
+ * can return valid data, such that clients can avoid doing any work computing the
+ * current theme or density if there's no chance that valid insets will be returned
+ */
+ public static final boolean INSETS_SUPPORTED = false;
+
+ /**
+ * Returns the insets of widgets with the given fully qualified name, in the given
+ * theme and the given screen density.
+ *
+ * @param fqcn the fully qualified name of the view
+ * @param density the screen density
+ * @param theme the theme name
+ * @return the insets of the visual bounds relative to the view info bounds, or null
+ * if not known or if there are no insets
+ */
+ public static Margins getInsets(String fqcn, Density density, String theme) {
+ if (INSETS_SUPPORTED) {
+ // Some sample data measured manually for common themes and widgets.
+ if (fqcn.equals(FQCN_BUTTON)) {
+ if (density == Density.HIGH) {
+ if (theme.startsWith(HOLO_PREFIX)) {
+ // Theme.Holo, Theme.Holo.Light, WVGA
+ return new Margins(5, 5, 5, 5);
+ } else {
+ // Theme.Light, WVGA
+ return new Margins(4, 4, 0, 7);
+ }
+ } else if (density == Density.MEDIUM) {
+ if (theme.startsWith(HOLO_PREFIX)) {
+ // Theme.Holo, Theme.Holo.Light, WVGA
+ return new Margins(3, 3, 3, 3);
+ } else {
+ // Theme.Light, HVGA
+ return new Margins(2, 2, 0, 4);
+ }
+ } else if (density == Density.LOW) {
+ if (theme.startsWith(HOLO_PREFIX)) {
+ // Theme.Holo, Theme.Holo.Light, QVGA
+ return new Margins(2, 2, 2, 2);
+ } else {
+ // Theme.Light, QVGA
+ return new Margins(1, 3, 0, 4);
+ }
+ }
+ } else if (fqcn.equals(FQCN_TOGGLE_BUTTON)) {
+ if (density == Density.HIGH) {
+ if (theme.startsWith(HOLO_PREFIX)) {
+ // Theme.Holo, Theme.Holo.Light, WVGA
+ return new Margins(5, 5, 5, 5);
+ } else {
+ // Theme.Light, WVGA
+ return new Margins(2, 2, 0, 5);
+ }
+ } else if (density == Density.MEDIUM) {
+ if (theme.startsWith(HOLO_PREFIX)) {
+ // Theme.Holo, Theme.Holo.Light, WVGA
+ return new Margins(3, 3, 3, 3);
+ } else {
+ // Theme.Light, HVGA
+ return new Margins(0, 1, 0, 3);
+ }
+ } else if (density == Density.LOW) {
+ if (theme.startsWith(HOLO_PREFIX)) {
+ // Theme.Holo, Theme.Holo.Light, QVGA
+ return new Margins(2, 2, 2, 2);
+ } else {
+ // Theme.Light, QVGA
+ return new Margins(2, 2, 0, 4);
+ }
+ }
+ } else if (fqcn.equals(FQCN_SPINNER)) {
+ if (density == Density.HIGH) {
+ if (!theme.startsWith(HOLO_PREFIX)) {
+ // Theme.Light, WVGA
+ return new Margins(3, 4, 2, 8);
+ } // Doesn't render on Holo!
+ } else if (density == Density.MEDIUM) {
+ if (!theme.startsWith(HOLO_PREFIX)) {
+ // Theme.Light, HVGA
+ return new Margins(1, 1, 0, 4);
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static final String HOLO_PREFIX = "Theme.Holo"; //$NON-NLS-1$
+}