| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.tools.idea.editors.theme.preview; |
| |
| import com.android.SdkConstants; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Objects; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Splitter; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Iterables; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.bootstrap.DOMImplementationRegistry; |
| import org.w3c.dom.ls.DOMImplementationLS; |
| import org.w3c.dom.ls.LSOutput; |
| import org.w3c.dom.ls.LSSerializer; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| import java.awt.*; |
| import java.io.PrintStream; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import static com.android.SdkConstants.*; |
| |
| public class ThemePreviewBuilder { |
| /** Namespace schema to use for attributes specific to the preview builder. */ |
| public static final String BUILDER_URI = "http://schemas.android.com/tools/preview/builder"; |
| public static final String BUILDER_NS_NAME = "builder"; |
| /** Attribute to use to specify the ComponentGroup that a specific item in the preview belongs to. */ |
| public static final String BUILDER_ATTR_GROUP = "group"; |
| |
| static final String THEME_PREVIEW_LAYOUT = "com.android.tools.idea.editors.theme.widgets.ThemePreviewLayout"; |
| |
| private static final Map<String, String> NAMESPACE_TO_URI = ImmutableMap.of( |
| ANDROID_NS_NAME, ANDROID_URI, |
| APP_PREFIX, AUTO_URI, |
| BUILDER_NS_NAME, BUILDER_URI |
| ); |
| |
| private final ArrayList<Predicate<ComponentDefinition>> myComponentFilters = new ArrayList<Predicate<ComponentDefinition>>(); |
| |
| /** |
| * Defines groups for the framework widgets. All the project widgets will go into "Custom". |
| */ |
| public enum ComponentGroup { |
| // The order of the groups is important since it will be the one used to display the preview. |
| TOOLBAR("App bar", VALUE_VERTICAL), |
| RAISED_BUTTON("Raised button", VALUE_VERTICAL), |
| FLAT_BUTTON("Flat button", VALUE_VERTICAL), |
| FAB_BUTTON("Fab button", VALUE_HORIZONTAL), |
| HORIZONTAL_PROGRESSBAR("Horizontal Progressbar", VALUE_HORIZONTAL), |
| INDETERMINATE_PROGRESSBAR("Progressbar (indeterminate)", VALUE_HORIZONTAL), |
| SLIDERS("Seekbar", VALUE_HORIZONTAL), |
| RADIO_BUTTON("Radiobutton", VALUE_HORIZONTAL), |
| CHECKBOX("Checkbox", VALUE_HORIZONTAL), |
| SWITCH("Switch", VALUE_HORIZONTAL), |
| TEXT("TextView", VALUE_VERTICAL), |
| NAVIGATION_BAR("Navigation bar", VALUE_HORIZONTAL), |
| STATUS_BAR("Status bar", VALUE_HORIZONTAL), |
| CUSTOM("Custom", VALUE_VERTICAL),; |
| |
| final String name; |
| final String orientation; |
| |
| /** |
| * Creates a new component group with the given name and orientation. |
| * @param name the group name |
| * @param orientation the group orientation. Must be either {@link SdkConstants.VALUE_VERTICAL} or {@link SdkConstants.VALUE_HORIZONTAL} |
| */ |
| ComponentGroup(@NotNull String name, @NotNull String orientation) { |
| this.name = name; |
| this.orientation = orientation; |
| } |
| } |
| |
| /** |
| * Class that contains the definition of an Android widget so it can be displayed on the preview. |
| */ |
| public static class ComponentDefinition { |
| private static final AtomicInteger ourCounter = new AtomicInteger(0); |
| |
| // Name and group identify the component |
| final String name; |
| final ComponentGroup group; |
| |
| private final int id; |
| final String description; |
| final HashMap<String, String> attributes = new HashMap<String, String>(); |
| |
| private final int weight; |
| int apiLevel; |
| List<String> aliases; |
| |
| public ComponentDefinition(String description, ComponentGroup group, String name, int weight) { |
| this.id = ourCounter.incrementAndGet(); |
| |
| this.description = description; |
| this.name = name; |
| this.group = group; |
| this.weight = weight; |
| } |
| |
| public ComponentDefinition(String description, ComponentGroup group, String name) { |
| this(description, group, name, 0); |
| } |
| |
| /** |
| * Set a component attribute. Most of the possible keys are defined in the {@link SdkConstants} class and they are of the form |
| * ATTR_*. |
| * @param ns the key namespace. |
| * @param key the attribute name. |
| * @param value the attribute value. |
| */ |
| @NotNull |
| public ComponentDefinition set(@Nullable String ns, @NotNull String key, @NotNull String value) { |
| String prefix = (ns != null ? (ns + ":") : ""); |
| attributes.put(prefix + key, value); |
| |
| return this; |
| } |
| |
| /** |
| * Set a component attribute. Most of the possible keys are defined in the {@link SdkConstants} class and they are of the form |
| * ATTR_*. |
| * @param key the attribute name. The key namespace will default to "android:" |
| * @param value the attribute value. |
| */ |
| @NotNull |
| public ComponentDefinition set(@NotNull String key, @NotNull String value) { |
| set(ANDROID_NS_NAME, key, value); |
| |
| return this; |
| } |
| |
| /** |
| * Set the API level this component is present in. This can be used to filter components that are not available in certain API levels. |
| */ |
| @NotNull |
| public ComponentDefinition setApiLevel(int apiLevel) { |
| this.apiLevel = apiLevel; |
| |
| return this; |
| } |
| |
| /** |
| * Return an Android id string that can be used when serializing the component to XML. |
| */ |
| String getId() { |
| return NEW_ID_PREFIX + "widget" + id; |
| } |
| |
| /** |
| * Sets the component text attribute. |
| */ |
| @NotNull |
| public ComponentDefinition setText(@NotNull String text) { |
| set(ATTR_TEXT, text); |
| return this; |
| } |
| |
| /** |
| * Adds a component name alias to help with text search. |
| */ |
| public ComponentDefinition addAlias(@NotNull String text) { |
| if (aliases == null) { |
| aliases = new ArrayList<String>(); |
| } |
| |
| aliases.add(text); |
| return this; |
| } |
| |
| Element build(@NotNull Document document) { |
| Element component = document.createElement(name); |
| |
| for (Map.Entry<String, String> entry : attributes.entrySet()) { |
| List<String> keyComponents = Splitter.on(":").limit(2).splitToList(entry.getKey()); |
| |
| if (keyComponents.size() != 1) { |
| component.setAttributeNS(NAMESPACE_TO_URI.get(keyComponents.get(0)), keyComponents.get(1), entry.getValue()); |
| } else { |
| component.setAttribute(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| setAttributeIfAbsent(component, ATTR_ID, getId()); |
| setAttributeIfAbsent(component, ATTR_TEXT, description); |
| setAttributeIfAbsent(component, ATTR_LAYOUT_WIDTH, VALUE_WRAP_CONTENT); |
| setAttributeIfAbsent(component, ATTR_LAYOUT_HEIGHT, VALUE_WRAP_CONTENT); |
| if (weight != 0) { |
| setAttributeIfAbsent(component, ATTR_LAYOUT_WEIGHT, Integer.toString(weight)); |
| } |
| setAttributeIfAbsent(component, ATTR_LAYOUT_GRAVITY, GRAVITY_VALUE_CENTER); |
| |
| if (!component.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_MARGIN) && |
| !component.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_MARGIN_LEFT) && |
| !component.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_MARGIN_RIGHT) && |
| !component.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_MARGIN_TOP) && |
| !component.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_MARGIN_BOTTOM) && |
| !component.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_MARGIN_START) && |
| !component.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_MARGIN_END)) { |
| // Default box around every component. |
| setAttribute(component, ATTR_LAYOUT_MARGIN_START, toDp(12)); |
| setAttribute(component, ATTR_LAYOUT_MARGIN_END, toDp(12)); |
| } |
| |
| component.setAttributeNS(BUILDER_URI, BUILDER_ATTR_GROUP, group.name()); |
| |
| return component; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| ComponentDefinition that = (ComponentDefinition)o; |
| return Objects.equal(group, that.group) && |
| Objects.equal(name, that.name); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(group, name); |
| } |
| } |
| |
| public static class ApiLevelFilter implements Predicate<ComponentDefinition> { |
| private final int myApiLevel; |
| |
| public ApiLevelFilter(int apiLevel) { |
| myApiLevel = apiLevel; |
| } |
| |
| @Override |
| public boolean apply(ComponentDefinition input) { |
| return input.apiLevel <= myApiLevel; |
| } |
| } |
| |
| /** |
| * Preview builder filter that returns components matching a certain string. |
| */ |
| public static class SearchFilter implements Predicate<ComponentDefinition> { |
| private final boolean myCaseSensitive; |
| private final String mySearchTerm; |
| |
| public SearchFilter(@NotNull String searchTerm, boolean caseSensitive) { |
| myCaseSensitive = caseSensitive; |
| mySearchTerm = caseSensitive ? searchTerm : searchTerm.toLowerCase(); |
| } |
| |
| public SearchFilter(@NotNull String searchTerm) { |
| this(searchTerm, false); |
| } |
| |
| @Override |
| public boolean apply(ComponentDefinition input) { |
| if (Strings.isNullOrEmpty(mySearchTerm)) { |
| return true; |
| } |
| |
| StringBuilder searchString = new StringBuilder(input.name); |
| |
| if (input.aliases != null) { |
| searchString.append(' ').append(Joiner.on(' ').join(input.aliases)); |
| } |
| searchString |
| .append(' ').append(input.description) |
| .append(' ').append(input.group.name); |
| |
| return myCaseSensitive |
| ? searchString.toString().contains(mySearchTerm) |
| : searchString.toString().toLowerCase().contains(mySearchTerm); |
| } |
| } |
| |
| // List containing all the pre-defined components that are displayed in the preview. |
| public static final List<ComponentDefinition> AVAILABLE_BASE_COMPONENTS = ImmutableList.of( |
| // Toolbar |
| new ToolbarComponentDefinition(false/*isAppCompat*/), |
| |
| // Buttons |
| new ComponentDefinition("Normal", ComponentGroup.RAISED_BUTTON, BUTTON), |
| /*new ComponentDefinition("Pressed", ComponentGroup.RAISED_BUTTON, BUTTON) |
| .set(ATTR_BACKGROUND, "@drawable/abc_btn_default_mtrl_shape"),*/ |
| new ComponentDefinition("Disabled", ComponentGroup.RAISED_BUTTON, BUTTON) |
| .set(ATTR_ENABLED, VALUE_FALSE), |
| |
| new ComponentDefinition("Normal", ComponentGroup.FLAT_BUTTON, BUTTON) |
| .set(null, ATTR_STYLE, "?android:attr/borderlessButtonStyle"), |
| /*new ComponentDefinition("Pressed", ComponentGroup.FLAT_BUTTON, BUTTON) |
| .set(ATTR_BACKGROUND, "@drawable/abc_btn_default_mtrl_shape"),*/ |
| new ComponentDefinition("Disabled", ComponentGroup.FLAT_BUTTON, BUTTON) |
| .set(null, ATTR_STYLE, "?android:attr/borderlessButtonStyle") |
| .set(ATTR_ENABLED, VALUE_FALSE), |
| |
| new ComponentDefinition("Radio button", ComponentGroup.RADIO_BUTTON, RADIO_BUTTON) |
| .setText(""), |
| new ComponentDefinition("Pressed Radio button", ComponentGroup.RADIO_BUTTON, RADIO_BUTTON) |
| .set(ATTR_CHECKED, VALUE_TRUE).setText(""), |
| |
| new ComponentDefinition("Checkbox", ComponentGroup.CHECKBOX, CHECK_BOX) |
| .setText(""), |
| new ComponentDefinition("Pressed Checkbox", ComponentGroup.CHECKBOX, CHECK_BOX) |
| .set(ATTR_CHECKED, VALUE_TRUE).setText(""), |
| |
| |
| new ComponentDefinition("Switch", ComponentGroup.SWITCH, SWITCH) |
| .setApiLevel(14) |
| .setText(""), |
| new ComponentDefinition("On Switch", ComponentGroup.SWITCH, SWITCH) |
| .setApiLevel(14) |
| .set(ATTR_CHECKED, VALUE_TRUE).setText(""), |
| |
| // Text, |
| new ComponentDefinition("Large text", ComponentGroup.TEXT, TEXT_VIEW).set("textAppearance", "?android:attr/textAppearanceLarge"), |
| new ComponentDefinition("Medium text", ComponentGroup.TEXT, TEXT_VIEW).set("textAppearance", "?android:attr/textAppearanceMedium"), |
| new ComponentDefinition("Small text", ComponentGroup.TEXT, TEXT_VIEW).set("textAppearance", "?android:attr/textAppearanceSmall"), |
| |
| // Bars |
| new ComponentDefinition("Status bar", ComponentGroup.STATUS_BAR, "com.android.layoutlib.bridge.bars.StatusBar") |
| .set(ATTR_LAYOUT_WIDTH, VALUE_MATCH_PARENT), |
| new ComponentDefinition("Navigation bar", ComponentGroup.NAVIGATION_BAR, "com.android.layoutlib.bridge.bars.NavigationBar") |
| .set(ATTR_LAYOUT_HEIGHT, "@android:dimen/navigation_bar_height") |
| .set(ATTR_LAYOUT_WIDTH, VALUE_MATCH_PARENT), |
| |
| // Misc |
| new ComponentDefinition("ProgressBar", ComponentGroup.INDETERMINATE_PROGRESSBAR, PROGRESS_BAR) |
| .set("progress", "50") |
| .set(ATTR_LAYOUT_WIDTH, VALUE_MATCH_PARENT), |
| new ComponentDefinition("ProgressBar", ComponentGroup.HORIZONTAL_PROGRESSBAR, PROGRESS_BAR) |
| .set("progress", "50") |
| .set(ATTR_LAYOUT_WIDTH, VALUE_MATCH_PARENT) |
| .set(null, ATTR_STYLE, "?android:attr/progressBarStyleHorizontal"), |
| new ComponentDefinition("SeekBar", ComponentGroup.SLIDERS, SEEK_BAR) |
| .set("progress", "50") |
| .set(ATTR_LAYOUT_WIDTH, VALUE_MATCH_PARENT)); |
| |
| // All the sizes are defined in pixels so they are not rescaled depending on the selected device dpi. |
| private static final int GROUP_TITLE_FONT_SIZE = 11; |
| |
| private List<ComponentDefinition> myComponents = new ArrayList<ComponentDefinition>(); |
| private String myGroupHeaderColor = "@android:color/darker_gray"; |
| private String myBackgroundColor = "@android:color/darker_gray"; |
| private PrintStream myDebugPrintStream; |
| |
| @NotNull |
| private static Element buildGridLayoutElement(@NotNull Document document, |
| int maxColumns, |
| int minColumnWidth, |
| int maxColumnWidth, |
| int itemHorizontalMargin, |
| int itemVerticalMargin) { |
| Element layout = document.createElement(THEME_PREVIEW_LAYOUT); |
| setAttribute(layout, ATTR_LAYOUT_WIDTH, VALUE_WRAP_CONTENT); |
| setAttribute(layout, ATTR_LAYOUT_HEIGHT, VALUE_WRAP_CONTENT); |
| // Set the custom preview layout attributes |
| layout.setAttribute("max_columns", Integer.toString(maxColumns)); |
| // All values in DP |
| if (minColumnWidth >= 0) { |
| layout.setAttribute("min_column_width", Integer.toString(minColumnWidth)); |
| } |
| if (maxColumnWidth >= 0) { |
| layout.setAttribute("max_column_width", Integer.toString(maxColumnWidth)); |
| } |
| if (itemHorizontalMargin >= 0) { |
| layout.setAttribute("item_horizontal_margin", Integer.toString(itemHorizontalMargin)); |
| } |
| if (itemVerticalMargin >= 0) { |
| layout.setAttribute("item_vertical_margin", Integer.toString(itemVerticalMargin)); |
| } |
| |
| return layout; |
| } |
| |
| @NotNull |
| private static Element buildElementGroup(@NotNull Document document, |
| @NotNull ComponentGroup group, |
| @NotNull String groupColor, |
| @NotNull List<ComponentDefinition> components, |
| boolean supressErrors) { |
| Element componentGrouper = document.createElement(RELATIVE_LAYOUT); |
| setAttribute(componentGrouper, ATTR_LAYOUT_WIDTH, VALUE_WRAP_CONTENT); |
| setAttribute(componentGrouper, ATTR_LAYOUT_HEIGHT, VALUE_WRAP_CONTENT); |
| setAttribute(componentGrouper, ATTR_BACKGROUND, "?android:attr/colorBackground"); |
| setAttribute(componentGrouper, ATTR_PADDING_TOP, toDp(5)); |
| setAttribute(componentGrouper, ATTR_PADDING_RIGHT, toDp(10)); |
| setAttribute(componentGrouper, ATTR_PADDING_LEFT, toDp(10)); |
| setAttribute(componentGrouper, ATTR_PADDING_BOTTOM, toDp(10)); |
| componentGrouper.setAttributeNS(BUILDER_URI, BUILDER_ATTR_GROUP, group.name()); |
| |
| Element elementGroup = document.createElement(LINEAR_LAYOUT); |
| setAttribute(elementGroup, ATTR_LAYOUT_WIDTH, VALUE_MATCH_PARENT); |
| setAttribute(elementGroup, ATTR_LAYOUT_HEIGHT, VALUE_WRAP_CONTENT); |
| setAttribute(elementGroup, ATTR_ORIENTATION, group.orientation); |
| setAttribute(elementGroup, ATTR_LAYOUT_CENTER_IN_PARENT, VALUE_TRUE); |
| setAttribute(elementGroup, ATTR_LAYOUT_MARGIN_START, toDp(10)); |
| setAttribute(elementGroup, ATTR_LAYOUT_MARGIN_BOTTOM, toDp(50)); |
| setAttribute(elementGroup, ATTR_GRAVITY, GRAVITY_VALUE_CENTER); |
| elementGroup.setAttributeNS(BUILDER_URI, BUILDER_ATTR_GROUP, group.name()); |
| |
| Element groupTitle = document.createElement(TEXT_VIEW); |
| setAttribute(groupTitle, ATTR_LAYOUT_WIDTH, VALUE_MATCH_PARENT); |
| setAttribute(groupTitle, ATTR_LAYOUT_HEIGHT, VALUE_WRAP_CONTENT); |
| setAttribute(groupTitle, ATTR_TEXT_SIZE, toSp(GROUP_TITLE_FONT_SIZE)); |
| setAttribute(groupTitle, "textColor", groupColor); |
| setAttribute(groupTitle, "text", group.name); |
| setAttribute(groupTitle, ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, VALUE_TRUE); |
| groupTitle.setAttributeNS(BUILDER_URI, BUILDER_ATTR_GROUP, group.name()); |
| |
| for (ComponentDefinition definition : components) { |
| elementGroup.appendChild(definition.build(document)); |
| } |
| |
| componentGrouper.appendChild(elementGroup); |
| componentGrouper.appendChild(groupTitle); |
| |
| return componentGrouper; |
| } |
| |
| @NotNull |
| private List<ComponentDefinition> getComponentsByGroup(@NotNull final ComponentGroup group) { |
| return ImmutableList.copyOf(Iterables.filter(myComponents, new Predicate<ComponentDefinition>() { |
| @Override |
| public boolean apply(ComponentDefinition input) { |
| if (group != input.group) { |
| return false; |
| } |
| |
| for (Predicate<ComponentDefinition> filter : myComponentFilters) { |
| if (!filter.apply(input)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| })); |
| } |
| |
| private static void printDebug(@NotNull PrintStream out, @NotNull Document document) { |
| try { |
| DOMImplementationRegistry reg = DOMImplementationRegistry.newInstance(); |
| |
| DOMImplementationLS impl = (DOMImplementationLS)reg.getDOMImplementation("LS"); |
| LSSerializer serializer = impl.createLSSerializer(); |
| LSOutput lsOutput = impl.createLSOutput(); |
| lsOutput.setEncoding("UTF-8"); |
| lsOutput.setByteStream(out); |
| |
| serializer.write(document, lsOutput); |
| } |
| catch (ClassNotFoundException e) { |
| e.printStackTrace(out); |
| } |
| catch (InstantiationException e) { |
| e.printStackTrace(out); |
| } |
| catch (IllegalAccessException e) { |
| e.printStackTrace(out); |
| } |
| } |
| |
| /** |
| * Returns the passed number as a string with format %ddp. |
| */ |
| private static String toDp(int n) { |
| return String.format("%d" + UNIT_DP, n); |
| } |
| |
| /** |
| * Returns the passed number as a string with format %dsp. |
| */ |
| private static String toSp(int n) { |
| return String.format("%d" + UNIT_SP, n); |
| } |
| |
| /** |
| * Utility method that sets an attribute using the "android" namespace. |
| * @param element the element to set the attribute on |
| * @param name the attribute name |
| * @param value the attribute name |
| */ |
| private static void setAttribute(Element element, String name, String value) { |
| element.setAttributeNS(ANDROID_URI, name, value); |
| } |
| |
| /** |
| * Utility method that sets an attribute using the "android" namespace only if the attribute doesn't already exist in the Element. |
| * @param element the element to set the attribute on |
| * @param name the attribute name |
| * @param value the attribute name |
| */ |
| private static void setAttributeIfAbsent(Element element, String name, String value) { |
| if (!element.hasAttributeNS(ANDROID_URI, name)) { |
| setAttribute(element, name, value); |
| } |
| } |
| |
| @NotNull |
| public ThemePreviewBuilder addComponentFilter(@NotNull Predicate<ComponentDefinition> filter) { |
| myComponentFilters.add(filter); |
| |
| return this; |
| } |
| |
| @NotNull |
| public ThemePreviewBuilder addAllComponents(@NotNull List<ComponentDefinition> definitions) { |
| myComponents.addAll(definitions); |
| |
| return this; |
| } |
| |
| @NotNull |
| public ThemePreviewBuilder addComponent(@NotNull ComponentDefinition definition) { |
| addAllComponents(ImmutableList.of(definition)); |
| |
| return this; |
| } |
| |
| @NotNull |
| public ThemePreviewBuilder setGroupHeaderColor(@NotNull String color) { |
| myGroupHeaderColor = color; |
| |
| return this; |
| } |
| |
| @NotNull |
| public ThemePreviewBuilder setGroupHeaderColor(@NotNull Color color) { |
| setGroupHeaderColor('#' + Integer.toHexString(color.getRGB())); |
| |
| return this; |
| } |
| |
| public ThemePreviewBuilder setBackgroundColor(@NotNull String color) { |
| myBackgroundColor = color; |
| |
| return this; |
| } |
| |
| public ThemePreviewBuilder setBackgroundColor(@NotNull Color color) { |
| setBackgroundColor('#' + Integer.toHexString(color.getRGB())); |
| |
| return this; |
| } |
| |
| public ThemePreviewBuilder setDebug(@NotNull PrintStream printStream) { |
| myDebugPrintStream = printStream; |
| |
| return this; |
| } |
| |
| @NotNull |
| public Document build() throws ParserConfigurationException { |
| DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
| Document document = documentBuilder.newDocument(); |
| |
| // Background |
| Element backgroundLayout = document.createElement(LINEAR_LAYOUT); |
| setAttribute(backgroundLayout, ATTR_LAYOUT_WIDTH, VALUE_MATCH_PARENT); |
| setAttribute(backgroundLayout, ATTR_LAYOUT_HEIGHT, VALUE_MATCH_PARENT); |
| setAttribute(backgroundLayout, ATTR_PADDING_TOP, toDp(20)); |
| setAttribute(backgroundLayout, ATTR_PADDING_BOTTOM, toDp(20)); |
| setAttribute(backgroundLayout, ATTR_ORIENTATION, VALUE_VERTICAL); |
| setAttribute(backgroundLayout, ATTR_GRAVITY, GRAVITY_VALUE_CENTER_HORIZONTAL); |
| setAttribute(backgroundLayout, ATTR_BACKGROUND, myBackgroundColor); |
| |
| Element layout = |
| buildGridLayoutElement(document, 3, 270, 270, 25, 15); |
| backgroundLayout.appendChild(layout); |
| document.appendChild(backgroundLayout); |
| |
| // Iterate over all the possible classes. |
| for (ComponentGroup group : ComponentGroup.values()) { |
| List<ComponentDefinition> components = getComponentsByGroup(group); |
| if (components.isEmpty()) { |
| continue; |
| } |
| |
| Element elementGroup = buildElementGroup(document, group, myGroupHeaderColor, components, group == ComponentGroup.CUSTOM); |
| layout.appendChild(elementGroup); |
| } |
| |
| if (myDebugPrintStream != null) { |
| printDebug(myDebugPrintStream, document); |
| } |
| |
| return document; |
| } |
| } |