diff options
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.java | 856 |
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$ +} |