Home | History | Annotate | Download | only in gre
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.gre;
     18 
     19 import static com.android.SdkConstants.ANDROID_URI;
     20 import static com.android.SdkConstants.ATTR_ID;
     21 import static com.android.SdkConstants.FQCN_BUTTON;
     22 import static com.android.SdkConstants.FQCN_SPINNER;
     23 import static com.android.SdkConstants.FQCN_TOGGLE_BUTTON;
     24 import static com.android.SdkConstants.ID_PREFIX;
     25 import static com.android.SdkConstants.NEW_ID_PREFIX;
     26 import static com.android.SdkConstants.VIEW_FRAGMENT;
     27 import static com.android.SdkConstants.VIEW_INCLUDE;
     28 
     29 import com.android.annotations.VisibleForTesting;
     30 import com.android.ide.common.api.IViewMetadata.FillPreference;
     31 import com.android.ide.common.api.Margins;
     32 import com.android.ide.common.api.ResizePolicy;
     33 import com.android.ide.eclipse.adt.AdtPlugin;
     34 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     35 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
     36 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
     37 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     38 import com.android.resources.Density;
     39 import com.android.utils.Pair;
     40 
     41 import org.w3c.dom.Document;
     42 import org.w3c.dom.Element;
     43 import org.w3c.dom.Node;
     44 import org.w3c.dom.NodeList;
     45 import org.xml.sax.InputSource;
     46 
     47 import java.io.BufferedInputStream;
     48 import java.io.InputStream;
     49 import java.util.ArrayList;
     50 import java.util.Collection;
     51 import java.util.Collections;
     52 import java.util.HashMap;
     53 import java.util.HashSet;
     54 import java.util.Iterator;
     55 import java.util.List;
     56 import java.util.Locale;
     57 import java.util.Map;
     58 import java.util.Set;
     59 
     60 import javax.xml.parsers.DocumentBuilder;
     61 import javax.xml.parsers.DocumentBuilderFactory;
     62 
     63 /**
     64  * The {@link ViewMetadataRepository} contains additional metadata for Android view
     65  * classes
     66  */
     67 public class ViewMetadataRepository {
     68     private static final String PREVIEW_CONFIG_FILENAME = "rendering-configs.xml";  //$NON-NLS-1$
     69     private static final String METADATA_FILENAME = "extra-view-metadata.xml";  //$NON-NLS-1$
     70 
     71     /** Singleton instance */
     72     private static ViewMetadataRepository sInstance = new ViewMetadataRepository();
     73 
     74     /**
     75      * Returns the singleton instance
     76      *
     77      * @return the {@link ViewMetadataRepository}
     78      */
     79     public static ViewMetadataRepository get() {
     80         return sInstance;
     81     }
     82 
     83     /**
     84      * Ever increasing counter used to assign natural ordering numbers to views and
     85      * categories
     86      */
     87     private static int sNextOrdinal = 0;
     88 
     89     /**
     90      * List of categories (which contain views); constructed lazily so use
     91      * {@link #getCategories()}
     92      */
     93     private List<CategoryData> mCategories;
     94 
     95     /**
     96      * Map from class names to view data objects; constructed lazily so use
     97      * {@link #getClassToView}
     98      */
     99     private Map<String, ViewData> mClassToView;
    100 
    101     /** Hidden constructor: Create via factory {@link #get()} instead */
    102     private ViewMetadataRepository() {
    103     }
    104 
    105     /** Returns a map from class fully qualified names to {@link ViewData} objects */
    106     private Map<String, ViewData> getClassToView() {
    107         if (mClassToView == null) {
    108             int initialSize = 75;
    109             mClassToView = new HashMap<String, ViewData>(initialSize);
    110             List<CategoryData> categories = getCategories();
    111             for (CategoryData category : categories) {
    112                 for (ViewData view : category) {
    113                     mClassToView.put(view.getFcqn(), view);
    114                 }
    115             }
    116             assert mClassToView.size() <= initialSize;
    117         }
    118 
    119         return mClassToView;
    120     }
    121 
    122     /**
    123      * Returns an XML document containing rendering configurations for the various Android
    124      * views. The FQN of each view can be obtained via the
    125      * {@link #getFullClassName(Element)} method
    126      *
    127      * @return an XML document containing rendering elements
    128      */
    129     public Document getRenderingConfigDoc() {
    130         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    131         Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
    132         InputStream paletteStream = clz.getResourceAsStream(PREVIEW_CONFIG_FILENAME);
    133         InputSource is = new InputSource(paletteStream);
    134         try {
    135             factory.setNamespaceAware(true);
    136             factory.setValidating(false);
    137             factory.setIgnoringComments(true);
    138             DocumentBuilder builder = factory.newDocumentBuilder();
    139             return builder.parse(is);
    140         } catch (Exception e) {
    141             AdtPlugin.log(e, "Parsing palette file failed");
    142             return null;
    143         }
    144     }
    145 
    146     /**
    147      * Returns a fully qualified class name for an element in the rendering document
    148      * returned by {@link #getRenderingConfigDoc()}
    149      *
    150      * @param element the element to look up the fqcn for
    151      * @return the fqcn of the view the element represents a preview for
    152      */
    153     public String getFullClassName(Element element) {
    154         // We don't use the element tag name, because in some cases we have
    155         // an outer element to render some interesting inner element, such as a tab widget
    156         // (which must be rendered inside a tab host).
    157         //
    158         // Therefore, we instead use the convention that the id is the fully qualified
    159         // class name, with .'s replaced with _'s.
    160 
    161         // Special case: for tab host we aren't allowed to mess with the id
    162         String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
    163 
    164         if ("@android:id/tabhost".equals(id)) {
    165             // Special case to distinguish TabHost and TabWidget
    166             NodeList children = element.getChildNodes();
    167             if (children.getLength() > 1 && (children.item(1) instanceof Element)) {
    168                 Element child = (Element) children.item(1);
    169                 String childId = child.getAttributeNS(ANDROID_URI, ATTR_ID);
    170                 if ("@+id/android_widget_TabWidget".equals(childId)) {
    171                     return "android.widget.TabWidget"; // TODO: Tab widget!
    172                 }
    173             }
    174             return "android.widget.TabHost"; // TODO: Tab widget!
    175         }
    176 
    177         StringBuilder sb = new StringBuilder();
    178         int i = 0;
    179         if (id.startsWith(NEW_ID_PREFIX)) {
    180             i = NEW_ID_PREFIX.length();
    181         } else if (id.startsWith(ID_PREFIX)) {
    182             i = ID_PREFIX.length();
    183         }
    184 
    185         for (; i < id.length(); i++) {
    186             char c = id.charAt(i);
    187             if (c == '_') {
    188                 sb.append('.');
    189             } else {
    190                 sb.append(c);
    191             }
    192         }
    193 
    194         return sb.toString();
    195     }
    196 
    197     /** Returns an ordered list of categories and views, parsed from a metadata file */
    198     private List<CategoryData> getCategories() {
    199         if (mCategories == null) {
    200             mCategories = new ArrayList<CategoryData>();
    201 
    202             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    203             Class<ViewMetadataRepository> clz = ViewMetadataRepository.class;
    204             InputStream inputStream = clz.getResourceAsStream(METADATA_FILENAME);
    205             InputSource is = new InputSource(new BufferedInputStream(inputStream));
    206             try {
    207                 factory.setNamespaceAware(true);
    208                 factory.setValidating(false);
    209                 factory.setIgnoringComments(true);
    210                 DocumentBuilder builder = factory.newDocumentBuilder();
    211                 Document document = builder.parse(is);
    212                 Map<String, FillPreference> fillTypes = new HashMap<String, FillPreference>();
    213                 for (FillPreference pref : FillPreference.values()) {
    214                     fillTypes.put(pref.toString().toLowerCase(Locale.US), pref);
    215                 }
    216 
    217                 NodeList categoryNodes = document.getDocumentElement().getChildNodes();
    218                 for (int i = 0, n = categoryNodes.getLength(); i < n; i++) {
    219                     Node node = categoryNodes.item(i);
    220                     if (node.getNodeType() == Node.ELEMENT_NODE) {
    221                         Element element = (Element) node;
    222                         if (element.getNodeName().equals("category")) { //$NON-NLS-1$
    223                             String name = element.getAttribute("name"); //$NON-NLS-1$
    224                             CategoryData category = new CategoryData(name);
    225                             NodeList children = element.getChildNodes();
    226                             for (int j = 0, m = children.getLength(); j < m; j++) {
    227                                 Node childNode = children.item(j);
    228                                 if (childNode.getNodeType() == Node.ELEMENT_NODE) {
    229                                     Element child = (Element) childNode;
    230                                     ViewData view = createViewData(fillTypes, child,
    231                                             null, FillPreference.NONE, RenderMode.NORMAL, null);
    232                                     category.addView(view);
    233                                 }
    234                             }
    235                             mCategories.add(category);
    236                         }
    237                     }
    238                 }
    239             } catch (Exception e) {
    240                 AdtPlugin.log(e, "Invalid palette metadata"); //$NON-NLS-1$
    241             }
    242         }
    243 
    244         return mCategories;
    245     }
    246 
    247     private ViewData createViewData(Map<String, FillPreference> fillTypes,
    248             Element child, String defaultFqcn, FillPreference defaultFill,
    249             RenderMode defaultRender, String defaultSize) {
    250         String fqcn = child.getAttribute("class"); //$NON-NLS-1$
    251         if (fqcn.length() == 0) {
    252             fqcn = defaultFqcn;
    253         }
    254         String fill = child.getAttribute("fill"); //$NON-NLS-1$
    255         FillPreference fillPreference = null;
    256         if (fill.length() > 0) {
    257             fillPreference = fillTypes.get(fill);
    258         }
    259         if (fillPreference == null) {
    260             fillPreference = defaultFill;
    261         }
    262         String skip = child.getAttribute("skip"); //$NON-NLS-1$
    263         RenderMode renderMode = defaultRender;
    264         String render = child.getAttribute("render"); //$NON-NLS-1$
    265         if (render.length() > 0) {
    266             renderMode = RenderMode.get(render);
    267         }
    268         String displayName = child.getAttribute("name"); //$NON-NLS-1$
    269         if (displayName.length() == 0) {
    270             displayName = null;
    271         }
    272 
    273         String relatedTo = child.getAttribute("relatedTo"); //$NON-NLS-1$
    274         String topAttrs = child.getAttribute("topAttrs"); //$NON-NLS-1$
    275         String resize = child.getAttribute("resize"); //$NON-NLS-1$
    276         ViewData view = new ViewData(fqcn, displayName, fillPreference,
    277                 skip.length() == 0 ? false : Boolean.valueOf(skip),
    278                 renderMode, relatedTo, resize, topAttrs);
    279 
    280         String init = child.getAttribute("init"); //$NON-NLS-1$
    281         String icon = child.getAttribute("icon"); //$NON-NLS-1$
    282 
    283         view.setInitString(init);
    284         if (icon.length() > 0) {
    285             view.setIconName(icon);
    286         }
    287 
    288         // Nested variations?
    289         if (child.hasChildNodes()) {
    290             // Palette variations
    291             NodeList childNodes = child.getChildNodes();
    292             for (int k = 0, kl = childNodes.getLength(); k < kl; k++) {
    293                 Node variationNode = childNodes.item(k);
    294                 if (variationNode.getNodeType() == Node.ELEMENT_NODE) {
    295                     Element variation = (Element) variationNode;
    296                     ViewData variationView = createViewData(fillTypes, variation,
    297                             fqcn, fillPreference, renderMode, resize);
    298                     view.addVariation(variationView);
    299                 }
    300             }
    301         }
    302 
    303         return view;
    304     }
    305 
    306     /**
    307      * Computes the palette entries for the given {@link AndroidTargetData}, looking up the
    308      * available node descriptors, categorizing and sorting them.
    309      *
    310      * @param targetData the target data for which to compute palette entries
    311      * @param alphabetical if true, sort all items in alphabetical order
    312      * @param createCategories if true, organize the items into categories
    313      * @return a list of pairs where each pair contains of the category label and an
    314      *         ordered list of elements to be included in that category
    315      */
    316     public List<Pair<String, List<ViewElementDescriptor>>> getPaletteEntries(
    317             AndroidTargetData targetData, boolean alphabetical, boolean createCategories) {
    318         List<Pair<String, List<ViewElementDescriptor>>> result =
    319             new ArrayList<Pair<String, List<ViewElementDescriptor>>>();
    320 
    321         List<List<ViewElementDescriptor>> lists = new ArrayList<List<ViewElementDescriptor>>(2);
    322         LayoutDescriptors layoutDescriptors = targetData.getLayoutDescriptors();
    323         lists.add(layoutDescriptors.getViewDescriptors());
    324         lists.add(layoutDescriptors.getLayoutDescriptors());
    325 
    326         // First record map of FQCN to ViewElementDescriptor such that we can quickly
    327         // determine if a particular palette entry is available
    328         Map<String, ViewElementDescriptor> fqcnToDescriptor =
    329             new HashMap<String, ViewElementDescriptor>();
    330         for (List<ViewElementDescriptor> list : lists) {
    331             for (ViewElementDescriptor view : list) {
    332                 String fqcn = view.getFullClassName();
    333                 if (fqcn == null) {
    334                     // <view> and <merge> tags etc
    335                     fqcn = view.getUiName();
    336                 }
    337                 fqcnToDescriptor.put(fqcn, view);
    338             }
    339         }
    340 
    341         Set<ViewElementDescriptor> remaining = new HashSet<ViewElementDescriptor>(
    342                 layoutDescriptors.getViewDescriptors().size()
    343                 + layoutDescriptors.getLayoutDescriptors().size());
    344         remaining.addAll(layoutDescriptors.getViewDescriptors());
    345         remaining.addAll(layoutDescriptors.getLayoutDescriptors());
    346 
    347         // Now iterate in palette metadata order over the items in the palette and include
    348         // any that also appear as a descriptor
    349         List<ViewElementDescriptor> categoryItems = new ArrayList<ViewElementDescriptor>();
    350         for (CategoryData category : getCategories()) {
    351             if (createCategories) {
    352                 categoryItems = new ArrayList<ViewElementDescriptor>();
    353             }
    354             for (ViewData view : category) {
    355                 String fqcn = view.getFcqn();
    356                 ViewElementDescriptor descriptor = fqcnToDescriptor.get(fqcn);
    357                 if (descriptor != null) {
    358                     remaining.remove(descriptor);
    359                     if (view.getSkip()) {
    360                         continue;
    361                     }
    362 
    363                     if (view.getDisplayName() != null || view.getInitString().length() > 0) {
    364                         categoryItems.add(new PaletteMetadataDescriptor(descriptor,
    365                                 view.getDisplayName(), view.getInitString(), view.getIconName()));
    366                     } else {
    367                         categoryItems.add(descriptor);
    368                     }
    369 
    370                     if (view.hasVariations()) {
    371                         for (ViewData variation : view.getVariations()) {
    372                             String init = variation.getInitString();
    373                             String icon = variation.getIconName();
    374                             ViewElementDescriptor desc = new PaletteMetadataDescriptor(descriptor,
    375                                     variation.getDisplayName(), init, icon);
    376                             categoryItems.add(desc);
    377                         }
    378                     }
    379                 }
    380             }
    381 
    382             if (createCategories && categoryItems.size() > 0) {
    383                 if (alphabetical) {
    384                     Collections.sort(categoryItems);
    385                 }
    386                 result.add(Pair.of(category.getName(), categoryItems));
    387             }
    388         }
    389 
    390         if (remaining.size() > 0) {
    391             List<ViewElementDescriptor> otherItems =
    392                     new ArrayList<ViewElementDescriptor>(remaining);
    393             // Always sorted, we don't have a natural order for these unknowns
    394             Collections.sort(otherItems);
    395             if (createCategories) {
    396                 result.add(Pair.of("Other", otherItems));
    397             } else {
    398                 categoryItems.addAll(otherItems);
    399             }
    400         }
    401 
    402         if (!createCategories) {
    403             if (alphabetical) {
    404                 Collections.sort(categoryItems);
    405             }
    406             result.add(Pair.of("Views", categoryItems));
    407         }
    408 
    409         return result;
    410     }
    411 
    412     @VisibleForTesting
    413     Collection<String> getAllFqcns() {
    414         return getClassToView().keySet();
    415     }
    416 
    417     /**
    418      * Metadata holder for a particular category - contains the name of the category, its
    419      * ordinal (for natural/logical sorting order) and views contained in the category
    420      */
    421     private static class CategoryData implements Iterable<ViewData>, Comparable<CategoryData> {
    422         /** Category name */
    423         private final String mName;
    424         /** Views included in this category */
    425         private final List<ViewData> mViews = new ArrayList<ViewData>();
    426         /** Natural ordering rank */
    427         private final int mOrdinal = sNextOrdinal++;
    428 
    429         /** Constructs a new category with the given name */
    430         private CategoryData(String name) {
    431             super();
    432             mName = name;
    433         }
    434 
    435         /** Adds a new view into this category */
    436         private void addView(ViewData view) {
    437             mViews.add(view);
    438         }
    439 
    440         private String getName() {
    441             return mName;
    442         }
    443 
    444         // Implements Iterable<ViewData> such that we can use for-each on the category to
    445         // enumerate its views
    446         @Override
    447         public Iterator<ViewData> iterator() {
    448             return mViews.iterator();
    449         }
    450 
    451         // Implements Comparable<CategoryData> such that categories can be naturally sorted
    452         @Override
    453         public int compareTo(CategoryData other) {
    454             return mOrdinal - other.mOrdinal;
    455         }
    456     }
    457 
    458     /** Metadata holder for a view of a given fully qualified class name */
    459     private static class ViewData implements Comparable<ViewData> {
    460         /** The fully qualified class name of the view */
    461         private final String mFqcn;
    462         /** Fill preference of the view */
    463         private final FillPreference mFillPreference;
    464         /** Skip this item in the palette? */
    465         private final boolean mSkip;
    466         /** Must this item be rendered alone? skipped? etc */
    467         private final RenderMode mRenderMode;
    468         /** Related views */
    469         private final String mRelatedTo;
    470         /** The relative rank of the view for natural ordering */
    471         private final int mOrdinal = sNextOrdinal++;
    472         /** List of optional variations */
    473         private List<ViewData> mVariations;
    474         /** Display name. Can be null. */
    475         private String mDisplayName;
    476         /**
    477          * Optional initialization string - a comma separate set of name/value pairs to
    478          * initialize the element with
    479          */
    480         private String mInitString;
    481         /** The name of an icon (known to the {@link IconFactory} to show for this view */
    482         private String mIconName;
    483         /** The resize preference of this view */
    484         private String mResize;
    485         /** The most commonly set attributes of this view */
    486         private String mTopAttrs;
    487 
    488         /** Constructs a new view data for the given class */
    489         private ViewData(String fqcn, String displayName,
    490                 FillPreference fillPreference, boolean skip, RenderMode renderMode,
    491                 String relatedTo, String resize, String topAttrs) {
    492             super();
    493             mFqcn = fqcn;
    494             mDisplayName = displayName;
    495             mFillPreference = fillPreference;
    496             mSkip = skip;
    497             mRenderMode = renderMode;
    498             mRelatedTo = relatedTo;
    499             mResize = resize;
    500             mTopAttrs = topAttrs;
    501         }
    502 
    503         /** Returns the {@link FillPreference} for views of this type */
    504         private FillPreference getFillPreference() {
    505             return mFillPreference;
    506         }
    507 
    508         /** Fully qualified class name of views of this type */
    509         private String getFcqn() {
    510             return mFqcn;
    511         }
    512 
    513         private String getDisplayName() {
    514             return mDisplayName;
    515         }
    516 
    517         private String getResize() {
    518             return mResize;
    519         }
    520 
    521         // Implements Comparable<ViewData> such that views can be sorted naturally
    522         @Override
    523         public int compareTo(ViewData other) {
    524             return mOrdinal - other.mOrdinal;
    525         }
    526 
    527         public RenderMode getRenderMode() {
    528             return mRenderMode;
    529         }
    530 
    531         public boolean getSkip() {
    532             return mSkip;
    533         }
    534 
    535         public List<String> getRelatedTo() {
    536             if (mRelatedTo == null || mRelatedTo.length() == 0) {
    537                 return Collections.emptyList();
    538             } else {
    539                 String[] basenames = mRelatedTo.split(","); //$NON-NLS-1$
    540                 List<String> result = new ArrayList<String>();
    541                 ViewMetadataRepository repository = ViewMetadataRepository.get();
    542                 Map<String, ViewData> classToView = repository.getClassToView();
    543 
    544                 List<String> fqns = new ArrayList<String>(classToView.keySet());
    545                 for (String basename : basenames) {
    546                     boolean found = false;
    547                     for (String fqcn : fqns) {
    548                         String suffix = '.' + basename;
    549                         if (fqcn.endsWith(suffix)) {
    550                             result.add(fqcn);
    551                             found = true;
    552                             break;
    553                         }
    554                     }
    555                     if (basename.equals(VIEW_FRAGMENT) || basename.equals(VIEW_INCLUDE)) {
    556                         result.add(basename);
    557                     } else {
    558                         assert found : basename;
    559                     }
    560                 }
    561 
    562                 return result;
    563             }
    564         }
    565 
    566         public List<String> getTopAttributes() {
    567             // "id" is a top attribute for all views, so it is not included in the XML, we just
    568             // add it in dynamically here
    569             if (mTopAttrs == null || mTopAttrs.length() == 0) {
    570                 return Collections.singletonList(ATTR_ID);
    571             } else {
    572                 String[] split = mTopAttrs.split(","); //$NON-NLS-1$
    573                 List<String> topAttributes = new ArrayList<String>(split.length + 1);
    574                 topAttributes.add(ATTR_ID);
    575                 for (int i = 0, n = split.length; i < n; i++) {
    576                     topAttributes.add(split[i]);
    577                 }
    578                 return Collections.<String>unmodifiableList(topAttributes);
    579             }
    580         }
    581 
    582         void addVariation(ViewData variation) {
    583             if (mVariations == null) {
    584                 mVariations = new ArrayList<ViewData>(4);
    585             }
    586             mVariations.add(variation);
    587         }
    588 
    589         List<ViewData> getVariations() {
    590             return mVariations;
    591         }
    592 
    593         boolean hasVariations() {
    594             return mVariations != null && mVariations.size() > 0;
    595         }
    596 
    597         private void setInitString(String initString) {
    598             this.mInitString = initString;
    599         }
    600 
    601         private String getInitString() {
    602             return mInitString;
    603         }
    604 
    605         private void setIconName(String iconName) {
    606             this.mIconName = iconName;
    607         }
    608 
    609         private String getIconName() {
    610             return mIconName;
    611         }
    612     }
    613 
    614     /**
    615      * Returns the {@link FillPreference} for classes with the given fully qualified class
    616      * name
    617      *
    618      * @param fqcn the fully qualified class name of the view
    619      * @return a suitable {@link FillPreference} for the given view type
    620      */
    621     public FillPreference getFillPreference(String fqcn) {
    622         ViewData view = getClassToView().get(fqcn);
    623         if (view != null) {
    624             return view.getFillPreference();
    625         }
    626 
    627         return FillPreference.NONE;
    628     }
    629 
    630     /**
    631      * Returns the {@link RenderMode} for classes with the given fully qualified class
    632      * name
    633      *
    634      * @param fqcn the fully qualified class name
    635      * @return the {@link RenderMode} to use for previews of the given view type
    636      */
    637     public RenderMode getRenderMode(String fqcn) {
    638         ViewData view = getClassToView().get(fqcn);
    639         if (view != null) {
    640             return view.getRenderMode();
    641         }
    642 
    643         return RenderMode.NORMAL;
    644     }
    645 
    646     /**
    647      * Returns the {@link ResizePolicy} for the given class.
    648      *
    649      * @param fqcn the fully qualified class name of the target widget
    650      * @return the {@link ResizePolicy} for the widget, which will never be null (but may
    651      *         be the default of {@link ResizePolicy#full()} if no metadata is found for
    652      *         the given widget)
    653      */
    654     public ResizePolicy getResizePolicy(String fqcn) {
    655         ViewData view = getClassToView().get(fqcn);
    656         if (view != null) {
    657             String resize = view.getResize();
    658             if (resize != null && resize.length() > 0) {
    659                 if ("full".equals(resize)) { //$NON-NLS-1$
    660                     return ResizePolicy.full();
    661                 } else if ("none".equals(resize)) { //$NON-NLS-1$
    662                     return ResizePolicy.none();
    663                 } else if ("horizontal".equals(resize)) { //$NON-NLS-1$
    664                     return ResizePolicy.horizontal();
    665                 } else if ("vertical".equals(resize)) { //$NON-NLS-1$
    666                     return ResizePolicy.vertical();
    667                 } else if ("scaled".equals(resize)) { //$NON-NLS-1$
    668                     return ResizePolicy.scaled();
    669                 } else {
    670                     assert false : resize;
    671                 }
    672             }
    673         }
    674 
    675         return ResizePolicy.full();
    676     }
    677 
    678     /**
    679      * Returns true if classes with the given fully qualified class name should be hidden
    680      * or skipped from the palette
    681      *
    682      * @param fqcn the fully qualified class name
    683      * @return true if views of the given type should be hidden from the palette
    684      */
    685     public boolean getSkip(String fqcn) {
    686         ViewData view = getClassToView().get(fqcn);
    687         if (view != null) {
    688             return view.getSkip();
    689         }
    690 
    691         return false;
    692     }
    693 
    694     /**
    695      * Returns a list of the top (most commonly set) attributes of the given
    696      * view.
    697      *
    698      * @param fqcn the fully qualified class name
    699      * @return a list, never null but possibly empty, of popular attribute names
    700      *         (not including a namespace prefix)
    701      */
    702     public List<String> getTopAttributes(String fqcn) {
    703         ViewData view = getClassToView().get(fqcn);
    704         if (view != null) {
    705             return view.getTopAttributes();
    706         }
    707 
    708         return Collections.singletonList(ATTR_ID);
    709     }
    710 
    711     /**
    712      * Returns a set of fully qualified names for views that are closely related to the
    713      * given view
    714      *
    715      * @param fqcn the fully qualified class name
    716      * @return a list, never null but possibly empty, of views that are related to the
    717      *         view of the given type
    718      */
    719     public List<String> getRelatedTo(String fqcn) {
    720         ViewData view = getClassToView().get(fqcn);
    721         if (view != null) {
    722             return view.getRelatedTo();
    723         }
    724 
    725         return Collections.emptyList();
    726     }
    727 
    728     /** Render mode for palette preview */
    729     public enum RenderMode {
    730         /**
    731          * Render previews, and it can be rendered as a sibling of many other views in a
    732          * big linear layout
    733          */
    734         NORMAL,
    735         /** This view needs to be rendered alone */
    736         ALONE,
    737         /**
    738          * Skip this element; it doesn't work or does not produce any visible artifacts
    739          * (such as the basic layouts)
    740          */
    741         SKIP;
    742 
    743         /**
    744          * Returns the {@link RenderMode} for the given render XML attribute
    745          * value
    746          *
    747          * @param render the attribute value in the metadata XML file
    748          * @return a corresponding {@link RenderMode}, never null
    749          */
    750         public static RenderMode get(String render) {
    751             if ("alone".equals(render)) {       //$NON-NLS-1$
    752                 return ALONE;
    753             } else if ("skip".equals(render)) { //$NON-NLS-1$
    754                 return SKIP;
    755             } else {
    756                 return NORMAL;
    757             }
    758         }
    759     }
    760 
    761     /**
    762      * Are insets supported yet? This flag indicates whether the {@link #getInsets} method
    763      * can return valid data, such that clients can avoid doing any work computing the
    764      * current theme or density if there's no chance that valid insets will be returned
    765      */
    766     public static final boolean INSETS_SUPPORTED = false;
    767 
    768     /**
    769      * Returns the insets of widgets with the given fully qualified name, in the given
    770      * theme and the given screen density.
    771      *
    772      * @param fqcn the fully qualified name of the view
    773      * @param density the screen density
    774      * @param theme the theme name
    775      * @return the insets of the visual bounds relative to the view info bounds, or null
    776      *         if not known or if there are no insets
    777      */
    778     public static Margins getInsets(String fqcn, Density density, String theme) {
    779         if (INSETS_SUPPORTED) {
    780             // Some sample data measured manually for common themes and widgets.
    781             if (fqcn.equals(FQCN_BUTTON)) {
    782                 if (density == Density.HIGH) {
    783                     if (theme.startsWith(HOLO_PREFIX)) {
    784                         // Theme.Holo, Theme.Holo.Light, WVGA
    785                         return new Margins(5, 5, 5, 5);
    786                     } else {
    787                         // Theme.Light, WVGA
    788                         return new Margins(4, 4, 0, 7);
    789                     }
    790                 } else if (density == Density.MEDIUM) {
    791                     if (theme.startsWith(HOLO_PREFIX)) {
    792                         // Theme.Holo, Theme.Holo.Light, WVGA
    793                         return new Margins(3, 3, 3, 3);
    794                     } else {
    795                         // Theme.Light, HVGA
    796                         return new Margins(2, 2, 0, 4);
    797                     }
    798                 } else if (density == Density.LOW) {
    799                     if (theme.startsWith(HOLO_PREFIX)) {
    800                         // Theme.Holo, Theme.Holo.Light, QVGA
    801                         return new Margins(2, 2, 2, 2);
    802                     } else {
    803                         // Theme.Light, QVGA
    804                         return new Margins(1, 3, 0, 4);
    805                     }
    806                 }
    807             } else if (fqcn.equals(FQCN_TOGGLE_BUTTON)) {
    808                 if (density == Density.HIGH) {
    809                     if (theme.startsWith(HOLO_PREFIX)) {
    810                         // Theme.Holo, Theme.Holo.Light, WVGA
    811                         return new Margins(5, 5, 5, 5);
    812                     } else {
    813                         // Theme.Light, WVGA
    814                         return new Margins(2, 2, 0, 5);
    815                     }
    816                 } else if (density == Density.MEDIUM) {
    817                     if (theme.startsWith(HOLO_PREFIX)) {
    818                         // Theme.Holo, Theme.Holo.Light, WVGA
    819                         return new Margins(3, 3, 3, 3);
    820                     } else {
    821                         // Theme.Light, HVGA
    822                         return new Margins(0, 1, 0, 3);
    823                     }
    824                 } else if (density == Density.LOW) {
    825                     if (theme.startsWith(HOLO_PREFIX)) {
    826                         // Theme.Holo, Theme.Holo.Light, QVGA
    827                         return new Margins(2, 2, 2, 2);
    828                     } else {
    829                         // Theme.Light, QVGA
    830                         return new Margins(2, 2, 0, 4);
    831                     }
    832                 }
    833             } else if (fqcn.equals(FQCN_SPINNER)) {
    834                 if (density == Density.HIGH) {
    835                     if (!theme.startsWith(HOLO_PREFIX)) {
    836                         // Theme.Light, WVGA
    837                         return new Margins(3, 4, 2, 8);
    838                     } // Doesn't render on Holo!
    839                 } else if (density == Density.MEDIUM) {
    840                     if (!theme.startsWith(HOLO_PREFIX)) {
    841                         // Theme.Light, HVGA
    842                         return new Margins(1, 1, 0, 4);
    843                     }
    844                 }
    845             }
    846         }
    847 
    848         return null;
    849     }
    850 
    851     private static final String HOLO_PREFIX = "Theme.Holo"; //$NON-NLS-1$
    852 }
    853