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