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