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