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 */ get()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 */ ViewMetadataRepository()104 private ViewMetadataRepository() { 105 } 106 107 /** Returns a map from class fully qualified names to {@link ViewData} objects */ getClassToView()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 */ getRenderingConfigDoc()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 */ getFullClassName(Element element)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 getCategories()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 createViewData(Map<String, FillPreference> fillTypes, Element child, String defaultFqcn, FillPreference defaultFill, RenderMode defaultRender, String defaultSize)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 */ getPaletteEntries( AndroidTargetData targetData, boolean alphabetical, boolean createCategories)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 getAllFqcns()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 */ CategoryData(String name)435 private CategoryData(String name) { 436 super(); 437 mName = name; 438 } 439 440 /** Adds a new view into this category */ addView(ViewData view)441 private void addView(ViewData view) { 442 mViews.add(view); 443 } 444 getName()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 iterator()452 public Iterator<ViewData> iterator() { 453 return mViews.iterator(); 454 } 455 456 // Implements Comparable<CategoryData> such that categories can be naturally sorted 457 @Override compareTo(CategoryData other)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 */ ViewData(String fqcn, String displayName, FillPreference fillPreference, boolean skip, RenderMode renderMode, String relatedTo, String resize, String topAttrs)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 */ getFillPreference()509 private FillPreference getFillPreference() { 510 return mFillPreference; 511 } 512 513 /** Fully qualified class name of views of this type */ getFcqn()514 private String getFcqn() { 515 return mFqcn; 516 } 517 getDisplayName()518 private String getDisplayName() { 519 return mDisplayName; 520 } 521 getResize()522 private String getResize() { 523 return mResize; 524 } 525 526 // Implements Comparable<ViewData> such that views can be sorted naturally 527 @Override compareTo(ViewData other)528 public int compareTo(ViewData other) { 529 return mOrdinal - other.mOrdinal; 530 } 531 getRenderMode()532 public RenderMode getRenderMode() { 533 return mRenderMode; 534 } 535 getSkip()536 public boolean getSkip() { 537 return mSkip; 538 } 539 getRelatedTo()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 getTopAttributes()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 addVariation(ViewData variation)586 void addVariation(ViewData variation) { 587 if (mVariations == null) { 588 mVariations = new ArrayList<ViewData>(4); 589 } 590 mVariations.add(variation); 591 } 592 getVariations()593 List<ViewData> getVariations() { 594 return mVariations; 595 } 596 hasVariations()597 boolean hasVariations() { 598 return mVariations != null && mVariations.size() > 0; 599 } 600 setInitString(String initString)601 private void setInitString(String initString) { 602 this.mInitString = initString; 603 } 604 getInitString()605 private String getInitString() { 606 return mInitString; 607 } 608 setIconName(String iconName)609 private void setIconName(String iconName) { 610 this.mIconName = iconName; 611 } 612 getIconName()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 */ getFillPreference(String fqcn)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 */ getRenderMode(String fqcn)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 */ getResizePolicy(String fqcn)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 */ getSkip(String fqcn)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 */ getTopAttributes(String fqcn)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 */ getRelatedTo(String fqcn)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 */ get(String render)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 */ getInsets(String fqcn, Density density, String theme)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