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