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