| /* |
| * 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.uibuilder.handlers.ui; |
| |
| import com.android.ide.common.rendering.api.SessionParams; |
| import com.android.resources.ResourceFolderType; |
| import com.android.resources.ResourceType; |
| import com.android.tools.idea.configurations.Configuration; |
| import com.android.tools.idea.rendering.*; |
| import com.android.tools.idea.uibuilder.api.ViewEditor; |
| import com.intellij.ide.highlighter.XmlFileType; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiFileFactory; |
| import com.intellij.psi.XmlElementFactory; |
| import com.intellij.psi.xml.XmlAttribute; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.ui.components.JBLabel; |
| import com.intellij.uiDesigner.core.GridLayoutManager; |
| import org.jetbrains.android.facet.AndroidFacet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.awt.image.BufferedImage; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import static com.android.SdkConstants.*; |
| |
| public class AppBarConfigurationDialog extends JDialog { |
| // TODO: Remove the hardcoded AppBar height (192dp) and ID (appbar). |
| private static final String TAG_COORDINATOR_LAYOUT = // 1 = Prefix for android namespace |
| "<android.support.design.widget.CoordinatorLayout\n" + // 2 = Prefix for auto namespace |
| "%3$s" + // 3 = Namespace declarations |
| "%4$s" + // 4 = FitsSystemWindows |
| " %1$s:layout_width=\"match_parent\"\n" + |
| " %1$s:layout_height=\"match_parent\">\n" + |
| " <android.support.design.widget.AppBarLayout\n" + |
| " %1$s:id=\"@+id/appbar\"\n" + |
| "%4$s" + // 4 = FitsSystemWindows |
| " %1$s:layout_height=\"192dp\"\n" + |
| " %1$s:layout_width=\"match_parent\">\n" + |
| " <android.support.design.widget.CollapsingToolbarLayout\n" + |
| " %1$s:layout_width=\"match_parent\"\n" + |
| " %1$s:layout_height=\"match_parent\"\n" + |
| " %2$s:toolbarId=\"@+id/toolbar\"\n" + |
| " %2$s:layout_scrollFlags=\"%5$s\"\n" + // 5 = ScrollFlags in CollapsingToolbarLayout |
| "%6$s" + // 6 = ScrollInterpolator |
| " %2$s:contentScrim=\"?attr/colorPrimary\">\n" + |
| "%7$s" + // 7 = Optional background image |
| " <android.support.v7.widget.Toolbar\n" + |
| " %1$s:id=\"@+id/toolbar\"\n" + |
| " %1$s:layout_height=\"?attr/actionBarSize\"\n" + |
| " %1$s:layout_width=\"match_parent\">\n" + |
| " </android.support.v7.widget.Toolbar>\n" + |
| " </android.support.design.widget.CollapsingToolbarLayout>\n" + |
| " </android.support.design.widget.AppBarLayout>\n" + |
| " <android.support.v4.widget.NestedScrollView\n" + |
| " %1$s:layout_width=\"match_parent\"\n" + |
| " %1$s:layout_height=\"match_parent\"\n" + |
| "%8$s" + // 8 = behavior_overlapTop |
| "%9$s" + // 9 = scrollY position |
| " %2$s:layout_behavior=\"android.support.design.widget.AppBarLayout$ScrollingViewBehavior\">\n" + |
| "%10$s" + //10 = Page content as xml |
| " </android.support.v4.widget.NestedScrollView>\n" + |
| "%11$s" + //11 = Optional FAB |
| "</android.support.design.widget.CoordinatorLayout>\n"; |
| |
| private static final String TAG_COORDINATOR_WITH_TABS_LAYOUT = // 1 = Prefix for android namespace |
| "<android.support.design.widget.CoordinatorLayout\n" + // 2 = Prefix for auto namespace |
| "%3$s" + // 3 = Namespace declarations |
| " %1$s:layout_width=\"match_parent\"\n" + |
| " %1$s:layout_height=\"match_parent\">\n" + |
| " <android.support.design.widget.AppBarLayout\n" + |
| " %1$s:id=\"@+id/appbar\"\n" + |
| " %1$s:layout_height=\"wrap_content\"\n" + |
| " %1$s:layout_width=\"match_parent\">\n" + |
| " <android.support.v7.widget.Toolbar\n" + |
| " %1$s:layout_height=\"?attr/actionBarSize\"\n" + |
| " %1$s:layout_width=\"match_parent\"\n" + |
| " %2$s:layout_scrollFlags=\"scroll|enterAlways\">\n" + |
| " </android.support.v7.widget.Toolbar>\n" + |
| " <android.support.design.widget.TabLayout\n" + |
| " %1$s:id=\"@+id/tabs\"\n" + |
| " %1$s:layout_width=\"match_parent\"\n" + |
| " %1$s:layout_height=\"wrap_content\"\n" + |
| "%4$s" + // 4 = ScrollFlags for TabLayout |
| " %2$s:tabMode=\"scrollable\">\n" + |
| " </android.support.design.widget.TabLayout>\n" + |
| " </android.support.design.widget.AppBarLayout>\n" + |
| " <android.support.v4.widget.NestedScrollView\n" + |
| " %1$s:layout_width=\"match_parent\"\n" + |
| " %1$s:layout_height=\"match_parent\"\n" + |
| "%5$s" + // 5 = scrollY position |
| " %2$s:layout_behavior=\"android.support.design.widget.AppBarLayout$ScrollingViewBehavior\">\n" + |
| " %6$s\n" + // 6 = Page content as xml |
| " </android.support.v4.widget.NestedScrollView>\n" + |
| "%7$s" + // 7 = Optional FAB |
| "</android.support.design.widget.CoordinatorLayout>\n"; |
| |
| private static final String TAG_FLOATING_ACTION_BUTTON = // 1 = Prefix for android namespace |
| "<android.support.design.widget.FloatingActionButton\n" + // 2 = Prefix for auto namespace |
| " %1$s:layout_height=\"wrap_content\"\n" + |
| " %1$s:layout_width=\"wrap_content\"\n" + |
| " %2$s:layout_anchor=\"@id/appbar\"\n" + |
| " %2$s:layout_anchorGravity=\"bottom|right|end\"\n" + |
| " %1$s:src=\"%3$s\"\n" + // 3 = Image location |
| " %1$s:layout_marginRight=\"16dp\"\n" + |
| " %1$s:clickable=\"true\"\n" + |
| " %2$s:fabSize=\"mini\"/>\n"; |
| |
| private static final String TAG_IMAGE_VIEW = // 1 = Prefix for android namespace |
| "<ImageView\n" + |
| " %1$s:id=\"@+id/app_bar_image\"\n" + |
| " %1$s:layout_width=\"match_parent\"\n" + |
| " %1$s:layout_height=\"match_parent\"\n" + |
| "%2$s" + // 2 = Collapse mode |
| " %1$s:src=\"%3$s\"\n" + // 3 = Image src |
| " %1$s:scaleType=\"centerCrop\"/>\n"; |
| |
| private static final String TAG_TEXTVIEW = |
| "<TextView\n" + |
| " android:layout_width=\"match_parent\"\n" + |
| " android:layout_height=\"wrap_content\"\n" + |
| " android:text=\"%1$s\"\n" + // 1 = Text in the TextView |
| " android:padding=\"16dp\"/>"; |
| |
| private static final String DIALOG_TITLE = "Configure App Bar"; |
| private static final String DEFAULT_BACKGROUND_IMAGE = "@android:drawable/sym_def_app_icon"; |
| private static final String DEFAULT_FAB_IMAGE = "@android:drawable/ic_input_add"; |
| private static final String PREVIEW_PLACEHOLDER_FILE = "preview.xml"; |
| private static final String DUMMY_TEXT = "This text is present to test the Application Bar. "; |
| private static final int DUMMY_REPETITION = 200; |
| private static final String PREVIEW_HEADER = "Preview:"; |
| private static final String RENDER_ERROR = "An error happened during rendering..."; |
| private static final String OVERLAP_TOP_FORMAT = "%1$s:behavior_overlapTop=\"%2$s\""; |
| private static final double FUDGE_FACTOR = 0.95; |
| private static final int MIN_WIDTH = 40; |
| private static final int MIN_HEIGHT = 60; |
| private static final int START_WIDTH = 225; |
| private static final int START_HEIGHT = 400; |
| |
| private ViewEditor myEditor; |
| private JPanel myContentPane; |
| private JButton myButtonOK; |
| private JButton myButtonCancel; |
| private JBLabel myPreview; |
| private JCheckBox myCollapsing; |
| private JCheckBox myShowBackgroundImage; |
| private JCheckBox myFloatingActionButton; |
| private JCheckBox myFitStatusBar; |
| private JCheckBox myContentOverlap; |
| private JCheckBox myWithTabs; |
| private JButton myBackgroundImageSelector; |
| private JButton myFloatingActionButtonImageSelector; |
| private JPanel myPreviewPanel; |
| private JBLabel myCollapsedPreview; |
| private JBLabel myExpandedPreview; |
| private JBLabel myExpandedLabel; |
| private JBLabel myCollapsedLabel; |
| private JCheckBox myParallax; |
| private JTextField myContentOverlapAmount; |
| private boolean myWasAccepted; |
| private BufferedImage myExpandedImage; |
| private BufferedImage myCollapsedImage; |
| private String myBackgroundImage; |
| private String myFloatingActionButtonImage; |
| |
| public AppBarConfigurationDialog(@NotNull ViewEditor editor) { |
| myEditor = editor; |
| setTitle(DIALOG_TITLE); |
| setContentPane(myContentPane); |
| setModal(true); |
| getRootPane().setDefaultButton(myButtonOK); |
| myBackgroundImage = DEFAULT_BACKGROUND_IMAGE; |
| myFloatingActionButtonImage = DEFAULT_FAB_IMAGE; |
| final ActionListener updatePreviewListener = new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent actionEvent) { |
| updateControls(); |
| generatePreviews(); |
| } |
| }; |
| myWithTabs.addActionListener(updatePreviewListener); |
| myCollapsing.addActionListener(updatePreviewListener); |
| myShowBackgroundImage.addActionListener(updatePreviewListener); |
| myFloatingActionButton.addActionListener(updatePreviewListener); |
| myFitStatusBar.addActionListener(updatePreviewListener); |
| myParallax.addActionListener(updatePreviewListener); |
| myContentOverlap.addActionListener(updatePreviewListener); |
| myContentOverlapAmount.addActionListener(updatePreviewListener); |
| ((GridLayoutManager)myPreviewPanel.getLayout()).setRowStretch(0, 2); |
| |
| final ActionListener actionListener = new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| if (e.getSource() == myBackgroundImageSelector) { |
| String src = myEditor.displayResourceInput(EnumSet.of(ResourceType.DRAWABLE), null); |
| if (src != null) { |
| myBackgroundImage = src; |
| generatePreviews(); |
| } |
| } else if (e.getSource() == myFloatingActionButtonImageSelector) { |
| String src = myEditor.displayResourceInput(EnumSet.of(ResourceType.DRAWABLE), null); |
| if (src != null) { |
| myFloatingActionButtonImage = src; |
| generatePreviews(); |
| } |
| } else if (e.getSource() == myButtonOK) { |
| onOK(); |
| } else if (e.getSource() == myButtonCancel || |
| e.getSource() == myContentPane) { |
| onCancel(); |
| } |
| } |
| }; |
| myBackgroundImageSelector.addActionListener(actionListener); |
| myFloatingActionButtonImageSelector.addActionListener(actionListener); |
| myButtonOK.addActionListener(actionListener); |
| myButtonCancel.addActionListener(actionListener); |
| myContentPane.registerKeyboardAction(actionListener, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), |
| JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); |
| |
| myPreviewPanel.addComponentListener(new ComponentAdapter() { |
| @Override |
| public void componentResized(final ComponentEvent e) { |
| updatePreviewImages(); |
| } |
| }); |
| |
| setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); |
| addWindowListener(new WindowAdapter() { |
| @Override |
| public void windowClosing(WindowEvent e) { |
| onCancel(); |
| } |
| }); |
| } |
| |
| public boolean open(@NotNull XmlFile file) { |
| myCollapsedPreview.setMinimumSize(new Dimension(START_WIDTH, START_HEIGHT)); |
| myExpandedPreview.setMinimumSize(new Dimension(START_WIDTH, START_HEIGHT)); |
| pack(); |
| myCollapsedPreview.setMinimumSize(null); |
| myExpandedPreview.setMinimumSize(null); |
| Dimension size = getSize(); |
| Rectangle screen = getGraphicsConfiguration().getBounds(); |
| setLocation(screen.x + (screen.width - size.width)/2, screen.y + (screen.height - size.height)/2); |
| updateControls(); |
| generatePreviews(); |
| setVisible(true); |
| if (myWasAccepted) { |
| applyChanges(file); |
| } |
| return myWasAccepted; |
| } |
| |
| private void onOK() { |
| myWasAccepted = true; |
| dispose(); |
| } |
| |
| private void onCancel() { |
| dispose(); |
| } |
| |
| private void updateControls() { |
| myShowBackgroundImage.setEnabled(!myWithTabs.isSelected()); |
| myBackgroundImageSelector.setEnabled(!myWithTabs.isSelected() && myShowBackgroundImage.isSelected()); |
| myFitStatusBar.setEnabled(!myWithTabs.isSelected() && myShowBackgroundImage.isSelected()); |
| myParallax.setEnabled(!myWithTabs.isSelected() && myShowBackgroundImage.isSelected()); |
| myFitStatusBar.setEnabled(!myWithTabs.isSelected() && myShowBackgroundImage.isSelected()); |
| myContentOverlap.setEnabled(!myWithTabs.isSelected() && myShowBackgroundImage.isSelected()); |
| myContentOverlapAmount.setEnabled(!myWithTabs.isSelected() && myShowBackgroundImage.isSelected() && myContentOverlap.isSelected()); |
| myFloatingActionButtonImageSelector.setEnabled(myFloatingActionButton.isSelected()); |
| } |
| |
| private void generatePreviews() { |
| myCollapsedImage = generatePreview(true); |
| myExpandedImage = generatePreview(false); |
| updatePreviewImages(); |
| } |
| |
| private BufferedImage generatePreview(boolean collapsed) { |
| StringBuilder text = new StringBuilder(DUMMY_REPETITION * DUMMY_TEXT.length()); |
| for (int i=0; i<DUMMY_REPETITION; i++) { |
| text.append(DUMMY_TEXT); |
| } |
| String content = String.format(TAG_TEXTVIEW, text.toString()); |
| |
| Map<String, String> namespaces = getNameSpaces(null, collapsed); |
| String xml = getXml(content, collapsed, namespaces); |
| BufferedImage image = renderImage(xml, myEditor.getConfiguration()); |
| if (image != null && (image.getHeight() < MIN_HEIGHT || image.getWidth() < MIN_WIDTH)) { |
| image = null; |
| } |
| return image; |
| } |
| |
| private void updatePreviewImages() { |
| if (myExpandedImage == null || myCollapsedImage == null) { |
| myPreview.setText(RENDER_ERROR); |
| } else { |
| myPreview.setText(PREVIEW_HEADER); |
| } |
| updatePreviewImage(myCollapsedImage, myCollapsedPreview); |
| updatePreviewImage(myExpandedImage, myExpandedPreview); |
| } |
| |
| private void updatePreviewImage(BufferedImage image, JBLabel view) { |
| if (image == null) { |
| view.setIcon(null); |
| return; |
| } |
| double width = myPreviewPanel.getWidth() / 2; |
| double height = myPreviewPanel.getHeight() - myPreview.getHeight() - Math.max(myExpandedLabel.getHeight(), myCollapsedLabel.getHeight()); |
| if (width < MIN_WIDTH || height < MIN_HEIGHT) { |
| view.setIcon(null); |
| } |
| double scale = Math.min(width / image.getWidth(), height / image.getHeight()) * FUDGE_FACTOR; |
| image = ImageUtils.scale(image, scale, scale); |
| view.setIcon(new ImageIcon(image)); |
| } |
| |
| private void applyChanges(@NotNull XmlFile file) { |
| Map<String, String> namespaces = getNameSpaces(file.getRootTag(), false); |
| String xml = getXml(getDesignContent(file), false, namespaces); |
| XmlElementFactory elementFactory = XmlElementFactory.getInstance(file.getProject()); |
| XmlTag tag = elementFactory.createTagFromText(xml); |
| if (file.getRootTag() == null) { |
| file.add(tag); |
| } else { |
| file.getRootTag().replace(tag); |
| } |
| } |
| |
| @NotNull |
| private String getXml(@NotNull String content, boolean collapsed, @NotNull Map<String, String> namespaces) { |
| return myWithTabs.isSelected() ? getXmlWithTabs(content, collapsed, namespaces) : getXmlWithoutTabs(content, collapsed, namespaces); |
| } |
| |
| @NotNull |
| private String getXmlWithoutTabs(@NotNull String content, boolean collapsed, @NotNull Map<String, String> namespaces) { |
| return String.format( |
| TAG_COORDINATOR_LAYOUT, |
| namespaces.get(ANDROID_URI), |
| namespaces.get(AUTO_URI), |
| formatNamespaces(namespaces), |
| getFitsSystemWindows(namespaces), |
| getToolbarScrollBehavior(), |
| getInterpolator(namespaces), |
| getBackgroundImage(namespaces), |
| getBehaviorOverlapTop(namespaces), |
| getScrollPos(collapsed, namespaces), |
| content, |
| getFloatingActionButton(namespaces)); |
| } |
| |
| @NotNull |
| private String getXmlWithTabs(@NotNull String content, boolean collapsed, @NotNull Map<String, String> namespaces) { |
| return String.format( |
| TAG_COORDINATOR_WITH_TABS_LAYOUT, |
| namespaces.get(ANDROID_URI), |
| namespaces.get(AUTO_URI), |
| formatNamespaces(namespaces), |
| getTabLayoutScroll(namespaces), |
| getScrollPos(collapsed, namespaces), |
| content, |
| getFloatingActionButton(namespaces)); |
| } |
| |
| @NotNull |
| private String getFitsSystemWindows(@NotNull Map<String, String> namespaces) { |
| if (!myShowBackgroundImage.isSelected() || !myFitStatusBar.isSelected()) { |
| return ""; |
| } |
| return String.format(" %1$s:fitsSystemWindows=\"true\"\n", |
| namespaces.get(ANDROID_URI)); |
| } |
| |
| @NotNull |
| private String getToolbarScrollBehavior() { |
| return myCollapsing.isSelected() ? "scroll|enterAlways|enterAlwaysCollapsed" : "scroll|exitUntilCollapsed"; |
| } |
| |
| @NotNull |
| private String getTabLayoutScroll(@NotNull Map<String, String> namespaces) { |
| if (!myCollapsing.isSelected()) { |
| return ""; |
| } |
| return String.format(" %1$s:layout_scrollFlags=\"scroll|enterAlways\"\n", |
| namespaces.get(AUTO_URI)); |
| } |
| |
| @NotNull |
| private String getInterpolator(@NotNull Map<String, String> namespaces) { |
| if (!myShowBackgroundImage.isSelected()) { |
| return ""; |
| } |
| return String.format(" %2$s:layout_scrollInterpolator=\"@%1$s:anim/decelerate_interpolator\"\n", |
| namespaces.get(ANDROID_URI), |
| namespaces.get(AUTO_URI)); |
| } |
| |
| @NotNull |
| private String getBackgroundImage(@NotNull Map<String, String> namespaces) { |
| if (!myShowBackgroundImage.isSelected()) { |
| return ""; |
| } |
| return String.format(TAG_IMAGE_VIEW, |
| namespaces.get(ANDROID_URI), |
| getBackgroundImageCollapseMode(namespaces), |
| myBackgroundImage); |
| } |
| |
| @NotNull |
| private String getBehaviorOverlapTop(@NotNull Map<String, String> namespaces) { |
| if (!myContentOverlap.isSelected()) { |
| return ""; |
| } |
| return String.format(OVERLAP_TOP_FORMAT, |
| namespaces.get(AUTO_URI), |
| myContentOverlapAmount.getText()); |
| } |
| |
| @NotNull |
| private static String getScrollPos(boolean collapsed, @NotNull Map<String, String> namespaces) { |
| if (!collapsed) { |
| return ""; |
| } |
| return String.format(" %1$s:scrollY=\"830px\"\n", |
| namespaces.get(TOOLS_URI)); |
| } |
| |
| @NotNull |
| private String getBackgroundImageCollapseMode(@NotNull Map<String, String> namespaces) { |
| if (myParallax.isSelected()) { |
| return ""; |
| } |
| return String.format(" %1$s:layout_collapseMode=\"parallax\"\n", |
| namespaces.get(AUTO_URI)); |
| } |
| |
| @NotNull |
| private String getFloatingActionButton(@NotNull Map<String, String> namespaces) { |
| if (!myFloatingActionButton.isSelected()) { |
| return ""; |
| } |
| return String.format(TAG_FLOATING_ACTION_BUTTON, namespaces.get(ANDROID_URI), |
| namespaces.get(AUTO_URI), |
| myFloatingActionButtonImage); |
| } |
| |
| @NotNull |
| private static Map<String, String> getNameSpaces(@Nullable XmlTag root, boolean includeToolsNamespace) { |
| Map<String, String> reverse = new HashMap<String, String>(); |
| if (root != null) { |
| Map<String, String> namespaces = root.getLocalNamespaceDeclarations(); |
| for (String prefix : namespaces.keySet()) { |
| reverse.put(namespaces.get(prefix), prefix); |
| } |
| } |
| if (!reverse.containsKey(ANDROID_URI)) { |
| reverse.put(ANDROID_URI, ANDROID_NS_NAME); |
| } |
| if (!reverse.containsKey(AUTO_URI)) { |
| reverse.put(AUTO_URI, APP_PREFIX); |
| } |
| if (includeToolsNamespace && !reverse.containsKey(TOOLS_URI)) { |
| reverse.put(TOOLS_URI, TOOLS_PREFIX); |
| } |
| return reverse; |
| } |
| |
| @NotNull |
| private static String formatNamespaces(@NotNull Map<String, String> namespaces) { |
| StringBuilder result = new StringBuilder(); |
| for (String ns : namespaces.keySet()) { |
| String prefix = namespaces.get(ns); |
| result.append(String.format(" xmlns:%1$s=\"%2$s\"\n", prefix, ns)); |
| } |
| return result.toString(); |
| } |
| |
| // If AppBarLayout is applied a second time it should replace the current AppBarLayout: |
| @NotNull |
| private static String getDesignContent(@NotNull XmlFile file) { |
| XmlTag content = file.getRootTag(); |
| if (content != null && content.getName() != null && content.getName().equals(COORDINATOR_LAYOUT)) { |
| XmlTag root = content; |
| content = null; |
| for (XmlTag tag : root.getSubTags()) { |
| if (tag.getName() != null && |
| !tag.getName().equals(APP_BAR_LAYOUT) && |
| !tag.getName().equals(FLOATING_ACTION_BUTTON)) { |
| if (tag.getName().equals(CLASS_NESTED_SCROLL_VIEW)) { |
| content = tag.getSubTags().length > 0 ? tag.getSubTags()[0] : null; |
| } else { |
| content = tag; |
| } |
| break; |
| } |
| } |
| } |
| if (content == null) { |
| return ""; |
| } |
| // Remove any xmlns: attributes since this element will be added into the document |
| for (XmlAttribute attribute : content.getAttributes()) { |
| if (attribute != null && attribute.getName() != null && attribute.getName().startsWith(XMLNS_PREFIX)) { |
| attribute.delete(); |
| } |
| } |
| return content.getText(); |
| } |
| |
| private static BufferedImage renderImage(@NotNull String xml, @NotNull Configuration configuration) { |
| AndroidFacet facet = AndroidFacet.getInstance(configuration.getModule()); |
| if (facet == null) { |
| return null; |
| } |
| Project project = configuration.getModule().getProject(); |
| PsiFile file = PsiFileFactory.getInstance(project).createFileFromText(PREVIEW_PLACEHOLDER_FILE, XmlFileType.INSTANCE, xml); |
| RenderService renderService = RenderService.get(facet); |
| RenderLogger logger = renderService.createLogger(); |
| final RenderTask task = renderService.createTask(file, configuration, logger, null); |
| RenderResult result = null; |
| if (task != null) { |
| task.setRenderingMode(SessionParams.RenderingMode.NORMAL); |
| task.setFolderType(ResourceFolderType.LAYOUT); |
| result = task.render(); |
| task.dispose(); |
| } |
| if (result == null) { |
| return null; |
| } |
| return result.getRenderedImage(); |
| } |
| } |