org.moe.designer.rendering.RenderedPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.moe.designer.rendering.RenderedPanel.java

Source

/*
 * Copyright (C) 2014 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 org.moe.designer.rendering;

import com.android.ide.common.rendering.HardwareConfigHelper;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.common.rendering.api.ViewType;
import com.android.resources.ResourceFolderType;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.Screen;
import com.google.common.base.Objects;
import org.moe.designer.IXMLFileEditorProvider;
import org.moe.designer.configurations.Configuration;
import org.moe.designer.configurations.RenderContext;
import org.moe.designer.designSurface.graphics.DesignerGraphics;
import org.moe.designer.designSurface.graphics.DrawingStyle;
import org.moe.designer.ixml.IXmlFile;
import org.moe.designer.rendering.multi.RenderPreviewManager;
import org.moe.designer.rendering.multi.RenderPreviewMode;
import org.moe.designer.uipreview.RenderedView;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.xml.XmlFile;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLayeredPane;
import com.intellij.util.ui.AsyncProcessIcon;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import static com.android.SdkConstants.TAG_ITEM;
import static org.moe.designer.rendering.RenderErrorPanel.SIZE_ERROR_PANEL_DYNAMICALLY;

/** A panel displaying a layoutlib render result as well as errors */
public class RenderedPanel extends JPanel implements Disposable {
    private static final Integer LAYER_PROGRESS = JLayeredPane.POPUP_LAYER + 100;
    private static final boolean DEBUG_SHOW_VIEWS = false;

    protected RenderResult myRenderResult;
    protected RenderPreviewManager myPreviewManager;
    protected List<RenderedView> mySelectedViews;
    protected RenderContext myContext;

    private boolean myZoomToFit = true;
    private final List<ProgressIndicator> myProgressIndicators = new ArrayList<ProgressIndicator>();
    private final JComponent myImagePanel = new JComponent() {
    };
    @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
    private MyProgressPanel myProgressPanel;
    private RenderErrorPanel myErrorPanel;
    private int myErrorPanelHeight = -1;

    public RenderedPanel(boolean installSelectionListeners) {
        super(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, true));
        updateBackgroundColor();
        myImagePanel.setBackground(null);

        MyImagePanelWrapper previewPanel = new MyImagePanelWrapper();
        if (installSelectionListeners) {
            previewPanel.addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent mouseEvent) {
                    if (myRenderResult == null) {
                        return;
                    }

                    if (myRenderResult.getImage() != null) {
                        // Convert to model coordinates
                        int x1 = mouseEvent.getX();
                        int y1 = mouseEvent.getY();
                        x1 -= myImagePanel.getX();
                        y1 -= myImagePanel.getY();

                        Point p = fromScreenToModel(x1, y1);
                        if (p == null) {
                            return;
                        }

                        selectViewAt(p.x, p.y);
                    }
                }

                @Override
                public void mouseClicked(MouseEvent mouseEvent) {
                    if (myRenderResult == null) {
                        return;
                    }

                    if (mouseEvent.getClickCount() == 2) {
                        // Double click: open in the UI editor
                        switchToLayoutEditor();
                    }
                }
            });
        }

        add(previewPanel);

        myErrorPanel = new RenderErrorPanel();
        myErrorPanel.setVisible(false);
        previewPanel.add(myErrorPanel, JLayeredPane.POPUP_LAYER);
        myProgressPanel = new MyProgressPanel();
        previewPanel.add(myProgressPanel, LAYER_PROGRESS);
    }

    public void setRenderContext(@Nullable RenderContext context) {
        myContext = context;
    }

    public Component getPaintComponent() {
        return myImagePanel;
    }

    private void switchToLayoutEditor() {
        if (myRenderResult != null) {
            ApplicationManager.getApplication().invokeLater(new Runnable() {
                @Override
                public void run() {
                    VirtualFile file = myRenderResult.getFile().getVirtualFile();
                    if (file != null) {
                        Project project = myRenderResult.getModule().getProject();
                        FileEditorManager.getInstance(project).setSelectedEditor(file,
                                IXMLFileEditorProvider.ANDROID_DESIGNER_ID);
                    }
                }
            }, ModalityState.NON_MODAL);
        }
    }

    protected void selectViewAt(int x, int y) {
        RenderedView leaf = findLeaf(x, y, false);

        if (handleMenu(leaf)) {
            return;
        }

        while (leaf != null && leaf.tag == null) {
            leaf = leaf.getParent();
        }

        selectView(leaf);
    }

    private boolean handleMenu(@Nullable RenderedView leaf) {
        boolean showMenu = false;
        if (leaf != null) {
            ViewInfo view = leaf.view;
            if (view != null) {
                ViewType viewType = view.getViewType();
                if (viewType != ViewType.USER) {
                    IXmlFile xmlFile = myContext.getXmlFile();
                    if (ResourceHelper.getFolderType(xmlFile) == ResourceFolderType.MENU) {
                        // When rendering a menu file, don't hide menu when clicking outside of it
                        showMenu = true;
                    }
                    if (viewType == ViewType.ACTION_BAR_OVERFLOW) {
                        showMenu = !ActionBarHandler.isShowingMenu(myContext);
                    } else if (ActionBarHandler.isShowingMenu(myContext)) {
                        RenderedView v = leaf.getParent();
                        while (v != null) {
                            if (v.tag != null) {
                                // A view *containing* a system view is the menu
                                showMenu = true;
                                if (TAG_ITEM.equals(v.tag.getName())) {
                                    PsiFile file = v.tag.getContainingFile();
                                    if (file != null && file != xmlFile) {
                                        VirtualFile virtualFile = file.getVirtualFile();
                                        if (virtualFile != null) {
                                            Project project = file.getProject();
                                            int offset = v.tag.getTextOffset();
                                            OpenFileDescriptor descriptor = new OpenFileDescriptor(project,
                                                    virtualFile, offset);
                                            FileEditorManager.getInstance(project).openEditor(descriptor, true);
                                            return true;
                                        }
                                    }
                                }
                                break;
                            }
                            v = v.getParent();
                        }
                    }
                }
            }
        }

        ActionBarHandler.showMenu(showMenu, myContext, true);

        return false;
    }

    @Nullable
    protected RenderedView findLeaf(int x, int y, boolean requireTag) {
        RenderedViewHierarchy hierarchy = myRenderResult.getHierarchy();
        assert hierarchy != null; // because image != null
        RenderedView leaf = hierarchy.findLeafAt(x, y);

        // If you've clicked on for example a list item, the view you clicked
        // on may not correspond to a tag, it could be a designtime preview item,
        // so search upwards for the nearest surrounding tag
        if (requireTag) {
            while (leaf != null && leaf.tag == null) {
                leaf = leaf.getParent();
            }
        }

        return leaf;
    }

    protected void selectView(@Nullable RenderedView leaf) {
    }

    /**
     * Computes the corresponding layoutlib point from a screen point (relative to the top left corner of the rendered image)
     */
    @Nullable
    public Point fromScreenToModel(int x, int y) {
        if (myRenderResult == null) {
            return null;
        }
        RenderedImage image = myRenderResult.getImage();
        if (image != null) {
            double zoomFactor = image.getScale();
            Rectangle imageBounds = image.getImageBounds();
            if (imageBounds != null) {
                x -= imageBounds.x;
                y -= imageBounds.y;
                double deviceFrameFactor = imageBounds.getWidth() / (double) image.getScaledWidth();
                zoomFactor *= deviceFrameFactor;
            }

            x /= zoomFactor;
            y /= zoomFactor;

            return new Point(x, y);
        }

        return null;
    }

    /**
     * Computes the corresponding layoutlib point from a screen point (relative to the top left corner of the rendered image)
     */
    @Nullable
    public Rectangle fromScreenToModel(int x, int y, int width, int height) {
        if (myRenderResult == null) {
            return null;
        }
        RenderedImage image = myRenderResult.getImage();
        if (image != null) {
            double zoomFactor = image.getScale();
            Rectangle imageBounds = image.getImageBounds();
            if (imageBounds != null) {
                x -= imageBounds.x;
                y -= imageBounds.y;
                double deviceFrameFactor = imageBounds.getWidth() / (double) image.getScaledWidth();
                zoomFactor *= deviceFrameFactor;
            }

            x /= zoomFactor;
            y /= zoomFactor;
            width /= zoomFactor;
            height /= zoomFactor;

            return new Rectangle(x, y, width, height);
        }

        return null;
    }

    /**
     * Computes the corresponding screen coordinates (relative to the top left corner of the rendered image)
     * for a layoutlib rectangle.
     */
    @Nullable
    public Rectangle fromModelToScreen(int x, int y, int width, int height) {
        if (myRenderResult == null) {
            return null;
        }
        RenderedImage image = myRenderResult.getImage();
        if (image != null) {
            double zoomFactor = image.getScale();
            Rectangle imageBounds = image.getImageBounds();
            if (imageBounds != null) {
                double deviceFrameFactor = imageBounds.getWidth() / (double) image.getScaledWidth();
                zoomFactor *= deviceFrameFactor;
            }

            x *= zoomFactor;
            y *= zoomFactor;
            width *= zoomFactor;
            height *= zoomFactor;

            if (imageBounds != null) {
                x += imageBounds.x;
                y += imageBounds.y;
            }

            return new Rectangle(x, y, width, height);
        }

        return null;
    }

    protected boolean paintRenderedImage(Component component, Graphics g, int px, int py) {
        if (myRenderResult == null) {
            return false;
        }
        RenderedImage image = myRenderResult.getImage();
        if (image != null) {
            image.paint(g, px, py);

            // Paint hierarchy
            if (DEBUG_SHOW_VIEWS) {
                paintViews(g, px, py);
            }

            List<RenderedView> selectedViews = mySelectedViews;
            if (selectedViews != null && !selectedViews.isEmpty() && !myErrorPanel.isVisible()) {
                Shape prevClip = g.getClip();
                Shape clip = null;
                Configuration configuration = myContext.getConfiguration();
                if (configuration != null) {
                    Device device = configuration.getDevice();
                    if (device != null && HardwareConfigHelper.isRound(device)) {
                        Screen screen = device.getDefaultHardware().getScreen();
                        int width = screen.getXDimension();
                        int height = screen.getYDimension();
                        Rectangle m = fromModelToScreen(0, 0, width, height);
                        if (m != null) {
                            clip = RenderedImage.getClip(device, m.x + px, m.y + py, m.width, m.height);
                            if (clip != null) {
                                g.setClip(clip);
                            }
                        }
                    }
                }

                for (RenderedView selected : selectedViews) {
                    Rectangle r = fromModelToScreen(selected.x, selected.y, selected.w, selected.h);
                    if (r == null) {
                        continue;
                    }
                    int x = r.x + px;
                    int y = r.y + py;
                    int w = r.width;
                    int h = r.height;

                    DesignerGraphics.drawFilledRect(DrawingStyle.SELECTION, g, x, y, w, h);
                }

                if (clip != null) {
                    g.setClip(prevClip);
                }
            }

            return true;
        }
        return false;
    }

    private void paintViews(Graphics g, int px, int py) {
        if (DEBUG_SHOW_VIEWS) {
            Graphics2D g2d = (Graphics2D) g;
            Composite prev = g2d.getComposite();
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f));
            RenderedViewHierarchy hierarchy = myRenderResult.getHierarchy();
            if (hierarchy != null) {
                for (RenderedView view : hierarchy.getRoots()) {
                    paintView(g, px, py, view);
                }
            }
            g2d.setComposite(prev);
        }
    }

    private void paintView(Graphics g, int px, int py, RenderedView view) {
        if (DEBUG_SHOW_VIEWS) {
            if (view.view == null || view.view.getViewType() == ViewType.USER) {
                return;
            }
            Rectangle bounds = view.getBounds();
            Rectangle r = fromModelToScreen(bounds.x, bounds.y, bounds.width, bounds.height);
            if (r == null) {
                return;
            }
            int x = r.x + px;
            int y = r.y + py;
            int w = Math.max(0, r.width - 1);
            int h = Math.max(0, r.height - 1);

            if (view.h <= 300) {
                //noinspection UseJBColor
                g.setColor(Color.RED);
                g.fillRect(x, y, w, h);
            }
            //noinspection UseJBColor
            g.setColor(Color.WHITE);
            g.drawRect(x, y, w, h);
            String className = view.view.getClassName();
            if (className != null) {
                className = className.substring(className.lastIndexOf('.') + 1);
                Shape clip = g.getClip();
                g.setClip(x, y, w, h);
                g.drawString(className, x, y + h);
                g.setClip(clip);
            }

            for (RenderedView child : view.getChildren()) {
                paintView(g, px, py, child);
            }
        }
    }

    public void setRenderResult(@NotNull final RenderResult renderResult) {
        double prevScale = myRenderResult != null && myRenderResult.getImage() != null
                ? myRenderResult.getImage().getScale()
                : 1;
        myRenderResult = renderResult;
        RenderedImage image = myRenderResult.getImage();
        if (image != null) {
            image.setDeviceFrameEnabled(myShowDeviceFrames && myRenderResult.getRenderService() != null
                    && myRenderResult.getRenderService().getRenderingMode() == SessionParams.RenderingMode.NORMAL
                    && myRenderResult.getRenderService().getShowDecorations());
            if (myPreviewManager != null && RenderPreviewMode.getCurrent() != RenderPreviewMode.NONE) {
                Dimension fixedRenderSize = myPreviewManager.getFixedRenderSize();
                if (fixedRenderSize != null) {
                    image.setMaxSize(fixedRenderSize.width, fixedRenderSize.height);
                    image.setUseLargeShadows(false);
                }
            }
            image.setScale(prevScale);
        }
        mySelectedViews = null;

        RenderLogger logger = myRenderResult.getLogger();
        if (logger.hasProblems()) {
            if (!myErrorPanel.isVisible()) {
                myErrorPanelHeight = -1;
            }
            myErrorPanel.showErrors(myRenderResult);
            myErrorPanel.setVisible(true);
        } else {
            myErrorPanel.setVisible(false);
        }

        repaint();

        // Ensure that if we have a a preview mode enabled, it's shown
        if (myPreviewManager != null && myPreviewManager.hasPreviews()) {
            myPreviewManager.renderPreviews();
        }
    }

    public RenderResult getRenderResult() {
        return myRenderResult;
    }

    public void setSelectedViews(@Nullable List<RenderedView> views) {
        if (!Objects.equal(views, mySelectedViews)) {
            mySelectedViews = views;
            repaint();
        }
    }

    public synchronized void registerIndicator(@NotNull ProgressIndicator indicator) {
        synchronized (myProgressIndicators) {
            myProgressIndicators.add(indicator);
            myProgressPanel.showProgressIcon();
        }
    }

    public void unregisterIndicator(@NotNull ProgressIndicator indicator) {
        synchronized (myProgressIndicators) {
            myProgressIndicators.remove(indicator);

            if (myProgressIndicators.size() == 0) {
                myProgressPanel.hideProgressIcon();
            }
        }
    }

    protected void doRevalidate() {
        revalidate();
        updateImageSize();
        repaint();
    }

    public void update() {
        revalidate();

        ApplicationManager.getApplication().invokeLater(new Runnable() {
            @Override
            public void run() {
                doRevalidate();
            }
        });
    }

    public void updateImageSize() {
        if (myRenderResult == null) {
            return;
        }
        updateBackgroundColor();

        RenderedImage image = myRenderResult.getImage();
        if (image == null) {
            myImagePanel.setSize(0, 0);
        } else {
            if (myZoomToFit) {
                double availableHeight = getPanelHeight();
                double availableWidth = getPanelWidth();
                final int MIN_SIZE = 200;
                if (myPreviewManager != null && availableWidth > MIN_SIZE) {
                    int previewWidth = myPreviewManager.computePreviewWidth();
                    availableWidth = Math.max(MIN_SIZE, availableWidth - previewWidth);
                }
                image.zoomToFit((int) availableWidth, (int) availableHeight, false, 0, 0);
            }

            myImagePanel.setSize(getScaledImageSize());
            repaint();
        }
    }

    private void updateBackgroundColor() {
        // Ensure the background color is right: light/dark when showing device chrome, gray when not
        boolean useGray = false;
        if (!myShowDeviceFrames) {
            useGray = true;
        } else if (myPreviewManager != null && RenderPreviewMode.getCurrent() != RenderPreviewMode.NONE) {
            // TODO: Don't do this if showing device frames or if we're not in Darcula!
            useGray = !RenderPreviewMode.getCurrent().showsDeviceFrames();
        } else {
            if (myRenderResult != null) {
                RenderedImage image = myRenderResult.getImage();
                if (image != null) {
                    Boolean framed = image.isFramed();
                    if (framed == null) {
                        return;
                    }
                    useGray = !framed;
                }
            }
        }
        Color background = useGray ? DrawingStyle.DESIGNER_BACKGROUND_COLOR : JBColor.WHITE;
        if (getBackground() != background) {
            setBackground(background);
        }
    }

    protected double getPanelHeight() {
        return getSize().getHeight() - 5;
    }

    protected double getPanelWidth() {
        return getSize().getWidth() - 5;
    }

    public void zoomOut() {
        myZoomToFit = false;
        if (myRenderResult.getImage() != null) {
            myRenderResult.getImage().zoomOut();
        }
        doRevalidate();
    }

    public void zoomIn() {
        myZoomToFit = false;
        if (myRenderResult.getImage() != null) {
            myRenderResult.getImage().zoomIn();
        }
        doRevalidate();
    }

    public void zoomActual() {
        myZoomToFit = false;
        if (myRenderResult.getImage() != null) {
            myRenderResult.getImage().zoomActual();
        }
        doRevalidate();
    }

    public void setZoomToFit(boolean zoomToFit) {
        myZoomToFit = zoomToFit;
        doRevalidate();
    }

    public boolean isZoomToFit() {
        return myZoomToFit;
    }

    @Override
    public void dispose() {
        if (myPreviewManager != null) {
            myPreviewManager.dispose();
            myPreviewManager = null;
        }
        myErrorPanel.dispose();
        myErrorPanel = null;
    }

    // RenderContext helpers

    @Nullable
    public Module getModule() {
        return myRenderResult != null ? myRenderResult.getModule() : null;
    }

    @Nullable
    public XmlFile getXmlFile() {
        return myRenderResult != null ? (XmlFile) myRenderResult.getFile() : null;
    }

    @Nullable
    public VirtualFile getVirtualFile() {
        return myRenderResult != null ? myRenderResult.getFile().getVirtualFile() : null;
    }

    public boolean hasAlphaChannel() {
        return myRenderResult.getImage() != null && !myRenderResult.getImage().hasAlphaChannel();
    }

    @NotNull
    public Component getComponent() {
        return this;
    }

    @NotNull
    public Dimension getFullImageSize() {
        if (myRenderResult != null) {
            RenderedImage scaledImage = myRenderResult.getImage();
            if (scaledImage != null) {
                return new Dimension(scaledImage.getOriginalWidth(), scaledImage.getOriginalHeight());
            }
        }

        return RenderContext.NO_SIZE;
    }

    @NotNull
    public Dimension getScaledImageSize() {
        if (myRenderResult != null) {
            RenderedImage scaledImage = myRenderResult.getImage();
            if (scaledImage != null) {
                return new Dimension(scaledImage.getScaledWidth(), scaledImage.getScaledHeight());
            }
        }

        return RenderContext.NO_SIZE;
    }

    public Component getRenderComponent() {
        return myImagePanel.getParent();
    }

    public void setPreviewManager(@Nullable RenderPreviewManager manager) {
        if (manager == myPreviewManager) {
            return;
        }
        Component renderComponent = getRenderComponent();
        if (myPreviewManager != null) {
            myPreviewManager.unregisterMouseListener(renderComponent);
            myPreviewManager.dispose();
        }
        myPreviewManager = manager;
        if (myPreviewManager != null) {
            myPreviewManager.registerMouseListener(renderComponent);
        }
    }

    @Nullable
    public RenderPreviewManager getPreviewManager(@Nullable RenderContext context, boolean createIfNecessary) {
        if (myPreviewManager == null && createIfNecessary && context != null) {
            setPreviewManager(new RenderPreviewManager(context));
        }

        return myPreviewManager;
    }

    public void setMaxSize(int width, int height) {
        RenderedImage scaledImage = myRenderResult.getImage();
        if (scaledImage != null) {
            scaledImage.setMaxSize(width, height);
            scaledImage.setUseLargeShadows(width <= 0);
            myImagePanel.revalidate();
        }
    }

    private boolean myShowDeviceFrames = true;

    public void setDeviceFramesEnabled(boolean on) {
        myShowDeviceFrames = on;
        if (myRenderResult != null) {
            RenderedImage image = myRenderResult.getImage();
            if (image != null) {
                image.setDeviceFrameEnabled(on);
            }
        }
    }

    /**
     * Layered pane which shows the rendered image, as well as (if applicable) an error message panel on top of the rendering
     * near the bottom
     */
    private class MyImagePanelWrapper extends JBLayeredPane {
        public MyImagePanelWrapper() {
            add(myImagePanel);
            setBackground(null);
            setOpaque(true);
        }

        @Override
        public void doLayout() {
            super.doLayout();
            positionErrorPanel();
            myProgressPanel.setBounds(0, 0, getWidth(), getHeight());

            if (myPreviewManager == null || !myPreviewManager.hasPreviews()) {
                centerComponents();
            } else {
                if (myRenderResult != null) {
                    RenderedImage image = myRenderResult.getImage();
                    if (image != null) {
                        int fixedWidth = image.getMaxWidth();
                        int fixedHeight = image.getMaxHeight();
                        if (fixedWidth > 0) {
                            myImagePanel.setLocation(Math.max(0, (fixedWidth - image.getScaledWidth()) / 2),
                                    2 + Math.max(0, (fixedHeight - image.getScaledHeight()) / 2));
                            return;
                        }
                    }
                }

                myImagePanel.setLocation(0, 0);
            }
        }

        private void centerComponents() {
            Rectangle bounds = getBounds();
            Point point = myImagePanel.getLocation();
            point.x = (bounds.width - myImagePanel.getWidth()) / 2;
            point.y = (bounds.height - myImagePanel.getHeight()) / 2;

            // If we're squeezing the image to fit, and there's a drop shadow showing
            // shift *some* space away from the tail portion of the drop shadow over to
            // the left to make the image look more balanced
            if (myRenderResult != null) {
                if (point.x <= 2) {
                    RenderedImage image = myRenderResult.getImage();
                    // If there's a drop shadow
                    if (image != null) {
                        if (image.hasDropShadow()) {
                            point.x += ShadowPainter.SHADOW_SIZE / 3;
                        }
                    }
                }
                if (point.y <= 2) {
                    RenderedImage image = myRenderResult.getImage();
                    // If there's a drop shadow
                    if (image != null) {
                        if (image.hasDropShadow()) {
                            point.y += ShadowPainter.SHADOW_SIZE / 3;
                        }
                    }
                }
            }
            myImagePanel.setLocation(point);
        }

        private void positionErrorPanel() {
            int height = getHeight();
            int width = getWidth();
            int size;
            if (SIZE_ERROR_PANEL_DYNAMICALLY) {
                if (myErrorPanelHeight == -1) {
                    // Make the layout take up to 3/4ths of the height, and at least 1/4th, but
                    // anywhere in between based on what the actual text requires
                    size = height * 3 / 4;
                    int preferredHeight = myErrorPanel.getPreferredHeight(width) + 8;
                    if (preferredHeight < size) {
                        size = Math.max(preferredHeight, Math.min(height / 4, size));
                        myErrorPanelHeight = size;
                    }
                } else {
                    size = myErrorPanelHeight;
                }
            } else {
                size = height / 2;
            }

            myErrorPanel.setSize(width, size);
            myErrorPanel.setLocation(0, height - size);
        }

        @Override
        protected void paintComponent(Graphics graphics) {
            paintRenderedImage(this, graphics, myImagePanel.getX(), myImagePanel.getY());

            if (myPreviewManager != null) {
                myPreviewManager.paint((Graphics2D) graphics);
            }

            super.paintComponent(graphics);
        }

        @Override
        public Dimension getPreferredSize() {
            return myImagePanel.getSize();
        }
    }

    /**
     * Panel which displays the progress icon. The progress icon can either be a large icon in the
     * center, when there is no rendering showing, or a small icon in the upper right corner when there
     * is a rendering. This is necessary because even though the progress icon looks good on some
     * renderings, depending on the layout theme colors it is invisible in other cases.
     */
    private class MyProgressPanel extends JPanel {
        private AsyncProcessIcon mySmallProgressIcon;
        private AsyncProcessIcon myLargeProgressIcon;
        private boolean mySmall;
        private boolean myProgressVisible;

        private MyProgressPanel() {
            super(new BorderLayout());
            setOpaque(false);
        }

        /** The "small" icon mode isn't just for the icon size; it's for the layout position too; see {@link #doLayout} */
        private void setSmallIcon(boolean small) {
            if (small != mySmall) {
                if (myProgressVisible && getComponentCount() != 0) {
                    AsyncProcessIcon oldIcon = getProgressIcon();
                    oldIcon.suspend();
                }
                mySmall = true;
                removeAll();
                AsyncProcessIcon icon = getProgressIcon();
                add(icon, BorderLayout.CENTER);
                if (myProgressVisible) {
                    icon.setVisible(true);
                    icon.resume();
                }
            }
        }

        public void showProgressIcon() {
            if (!myProgressVisible) {
                setSmallIcon(myRenderResult != null && myRenderResult.getImage() != null);
                myProgressVisible = true;
                setVisible(true);
                AsyncProcessIcon icon = getProgressIcon();
                if (getComponentCount() == 0) { // First time: haven't added icon yet?
                    add(getProgressIcon(), BorderLayout.CENTER);
                } else {
                    icon.setVisible(true);
                }
                icon.resume();
            }
        }

        public void hideProgressIcon() {
            if (myProgressVisible) {
                myProgressVisible = false;
                setVisible(false);
                AsyncProcessIcon icon = getProgressIcon();
                icon.setVisible(false);
                icon.suspend();
            }
        }

        @Override
        public void doLayout() {
            super.doLayout();

            if (!myProgressVisible) {
                return;
            }

            // Place the progress icon in the center if there's no rendering, and in the
            // upper right corner if there's a rendering. The reason for this is that the icon color
            // will depend on whether we're in a light or dark IDE theme, and depending on the rendering
            // in the layout it will be invisible. For example, in Darcula the icon is white, and if the
            // layout is rendering a white screen, the progress is invisible.
            AsyncProcessIcon icon = getProgressIcon();
            Dimension size = icon.getPreferredSize();
            if (mySmall) {
                icon.setBounds(getWidth() - size.width - 1, 1, size.width, size.height);
            } else {
                icon.setBounds(getWidth() / 2 - size.width / 2, getHeight() / 2 - size.height / 2, size.width,
                        size.height);
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return getProgressIcon().getPreferredSize();
        }

        @NotNull
        private AsyncProcessIcon getProgressIcon() {
            return getProgressIcon(mySmall);
        }

        @NotNull
        private AsyncProcessIcon getProgressIcon(boolean small) {
            if (small) {
                if (mySmallProgressIcon == null) {
                    mySmallProgressIcon = new AsyncProcessIcon("Android layout rendering");
                    Disposer.register(RenderedPanel.this, mySmallProgressIcon);
                }
                return mySmallProgressIcon;
            } else {
                if (myLargeProgressIcon == null) {
                    myLargeProgressIcon = new AsyncProcessIcon.Big("Android layout rendering");
                    Disposer.register(RenderedPanel.this, myLargeProgressIcon);
                }
                return myLargeProgressIcon;
            }
        }
    }
}