org.intellij.images.editor.impl.ImageEditorUI.java Source code

Java tutorial

Introduction

Here is the source code for org.intellij.images.editor.impl.ImageEditorUI.java

Source

/*
 * Copyright 2004-2005 Alexey Efimov
 *
 * 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.intellij.images.editor.impl;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.URL;
import java.util.Locale;

import javax.annotation.Nonnull;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.intellij.images.ImagesBundle;
import org.intellij.images.editor.ImageDocument;
import org.intellij.images.editor.ImageDocument.ScaledImageProvider;
import org.intellij.images.editor.ImageEditor;
import org.intellij.images.editor.ImageZoomModel;
import org.intellij.images.editor.actionSystem.ImageEditorActions;
import org.intellij.images.options.EditorOptions;
import org.intellij.images.options.GridOptions;
import org.intellij.images.options.Options;
import org.intellij.images.options.OptionsManager;
import org.intellij.images.options.TransparencyChessboardOptions;
import org.intellij.images.options.ZoomOptions;
import org.intellij.images.thumbnail.actionSystem.ThumbnailViewActions;
import org.intellij.images.ui.ImageComponent;
import org.intellij.images.ui.ImageComponentDecorator;
import org.intellij.images.vfs.IfsUtil;
import org.jetbrains.annotations.NonNls;

import javax.annotation.Nullable;
import com.intellij.ide.CopyPasteDelegator;
import com.intellij.ide.CopyPasteSupport;
import com.intellij.ide.CopyProvider;
import com.intellij.ide.DataManager;
import com.intellij.ide.DeleteProvider;
import com.intellij.ide.util.DeleteHandler;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.components.JBLayeredPane;
import com.intellij.ui.components.Magnificator;
import com.intellij.util.LazyInitializer.NotNullValue;
import com.intellij.util.ObjectUtils;
import com.intellij.util.SVGLoader;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.JBUI.ScaleContext;
import com.intellij.util.ui.UIUtil;

/**
 * Image editor UI
 *
 * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
 */
final class ImageEditorUI extends JPanel
        implements DataProvider, CopyProvider, ImageComponentDecorator, Disposable {
    @NonNls
    private static final String IMAGE_PANEL = "image";
    @NonNls
    private static final String ERROR_PANEL = "error";
    @NonNls
    private static final String ZOOM_FACTOR_PROP = "ImageEditor.zoomFactor";

    @Nullable
    private final ImageEditor editor;
    private final DeleteProvider deleteProvider;
    private final CopyPasteSupport copyPasteSupport;

    private final ImageZoomModel zoomModel = new ImageZoomModelImpl();
    private final ImageWheelAdapter wheelAdapter = new ImageWheelAdapter();
    private final ChangeListener changeListener = new DocumentChangeListener();
    private final ImageComponent imageComponent = new ImageComponent();
    private final JPanel contentPanel;
    private final JLabel infoLabel;

    private final PropertyChangeListener optionsChangeListener = new OptionsChangeListener();
    private final JScrollPane myScrollPane;

    ImageEditorUI(@Nullable ImageEditor editor) {
        this.editor = editor;

        imageComponent.addPropertyChangeListener(ZOOM_FACTOR_PROP,
                e -> imageComponent.setZoomFactor(getZoomModel().getZoomFactor()));
        Options options = OptionsManager.getInstance().getOptions();
        EditorOptions editorOptions = options.getEditorOptions();
        options.addPropertyChangeListener(optionsChangeListener);

        copyPasteSupport = editor != null ? new CopyPasteDelegator(editor.getProject(), this) {
            @Nonnull
            @Override
            protected PsiElement[] getSelectedElements() {
                DataContext dataContext = DataManager.getInstance().getDataContext(ImageEditorUI.this);
                return ObjectUtils.notNull(dataContext.getData(LangDataKeys.PSI_ELEMENT_ARRAY),
                        PsiElement.EMPTY_ARRAY);
            }
        } : null;
        deleteProvider = new DeleteHandler.DefaultDeleteProvider();

        ImageDocument document = imageComponent.getDocument();
        document.addChangeListener(changeListener);

        // Set options
        TransparencyChessboardOptions chessboardOptions = editorOptions.getTransparencyChessboardOptions();
        GridOptions gridOptions = editorOptions.getGridOptions();
        imageComponent.setTransparencyChessboardCellSize(chessboardOptions.getCellSize());
        imageComponent.setTransparencyChessboardWhiteColor(chessboardOptions.getWhiteColor());
        imageComponent.setTransparencyChessboardBlankColor(chessboardOptions.getBlackColor());
        imageComponent.setGridLineZoomFactor(gridOptions.getLineZoomFactor());
        imageComponent.setGridLineSpan(gridOptions.getLineSpan());
        imageComponent.setGridLineColor(gridOptions.getLineColor());

        // Create layout
        ImageContainerPane view = new ImageContainerPane(imageComponent);
        view.addMouseListener(new EditorMouseAdapter());
        view.addMouseListener(new FocusRequester());

        myScrollPane = ScrollPaneFactory.createScrollPane(view, true);
        myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
        myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        // Zoom by wheel listener
        myScrollPane.addMouseWheelListener(wheelAdapter);

        // Construct UI
        setLayout(new BorderLayout());

        ActionManager actionManager = ActionManager.getInstance();
        ActionGroup actionGroup = (ActionGroup) actionManager.getAction(ImageEditorActions.GROUP_TOOLBAR);
        ActionToolbar actionToolbar = actionManager.createActionToolbar(ImageEditorActions.ACTION_PLACE,
                actionGroup, true);

        // Make sure toolbar is 'ready' before it's added to component hierarchy
        // to prevent ActionToolbarImpl.updateActionsImpl(boolean, boolean) from increasing popup size unnecessarily
        actionToolbar.updateActionsImmediately();

        actionToolbar.setTargetComponent(this);

        JComponent toolbarPanel = actionToolbar.getComponent();
        toolbarPanel.addMouseListener(new FocusRequester());

        JLabel errorLabel = new JLabel(ImagesBundle.message("error.broken.image.file.format"),
                Messages.getErrorIcon(), SwingConstants.CENTER);

        JPanel errorPanel = new JPanel(new BorderLayout());
        errorPanel.add(errorLabel, BorderLayout.CENTER);

        contentPanel = new JPanel(new CardLayout());
        contentPanel.add(myScrollPane, IMAGE_PANEL);
        contentPanel.add(errorPanel, ERROR_PANEL);

        JPanel topPanel = new JPanel(new BorderLayout());
        topPanel.add(toolbarPanel, BorderLayout.WEST);
        infoLabel = new JLabel((String) null, SwingConstants.RIGHT);
        infoLabel.setBorder(JBUI.Borders.emptyRight(2));
        topPanel.add(infoLabel, BorderLayout.EAST);

        add(topPanel, BorderLayout.NORTH);
        add(contentPanel, BorderLayout.CENTER);

        myScrollPane.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                updateZoomFactor();
            }
        });

        updateInfo();
    }

    private void updateInfo() {
        ImageDocument document = imageComponent.getDocument();
        BufferedImage image = document.getValue();
        if (image != null) {
            ColorModel colorModel = image.getColorModel();
            String format = document.getFormat();
            if (format == null) {
                format = editor != null ? ImagesBundle.message("unknown.format") : "";
            } else {
                format = format.toUpperCase(Locale.ENGLISH);
            }
            VirtualFile file = editor != null ? editor.getFile() : null;
            infoLabel.setText(ImagesBundle.message("image.info", image.getWidth(), image.getHeight(), format,
                    colorModel.getPixelSize(), file != null ? StringUtil.formatFileSize(file.getLength()) : ""));
        } else {
            infoLabel.setText(null);
        }
    }

    @SuppressWarnings("UnusedDeclaration")
    JComponent getContentComponent() {
        return contentPanel;
    }

    ImageComponent getImageComponent() {
        return imageComponent;
    }

    public void dispose() {
        Options options = OptionsManager.getInstance().getOptions();
        options.removePropertyChangeListener(optionsChangeListener);

        imageComponent.removeMouseWheelListener(wheelAdapter);
        imageComponent.getDocument().removeChangeListener(changeListener);

        removeAll();
    }

    @Override
    public void setTransparencyChessboardVisible(boolean visible) {
        imageComponent.setTransparencyChessboardVisible(visible);
        repaint();
    }

    @Override
    public boolean isTransparencyChessboardVisible() {
        return imageComponent.isTransparencyChessboardVisible();
    }

    @Override
    public boolean isEnabledForActionPlace(String place) {
        // Disable for thumbnails action
        return !ThumbnailViewActions.ACTION_PLACE.equals(place);
    }

    @Override
    public void setGridVisible(boolean visible) {
        imageComponent.setGridVisible(visible);
        repaint();
    }

    @Override
    public boolean isGridVisible() {
        return imageComponent.isGridVisible();
    }

    public ImageZoomModel getZoomModel() {
        return zoomModel;
    }

    public void setImageProvider(ScaledImageProvider imageProvider, String format) {
        ImageDocument document = imageComponent.getDocument();
        BufferedImage previousImage = document.getValue();
        document.setValue(imageProvider);
        if (imageProvider == null) {
            return;
        }
        document.setFormat(format);

        if (previousImage == null || !zoomModel.isZoomLevelChanged()) {
            Options options = OptionsManager.getInstance().getOptions();
            ZoomOptions zoomOptions = options.getEditorOptions().getZoomOptions();

            if (!(zoomOptions.isSmartZooming() && updateZoomFactor())) {
                zoomModel.setZoomFactor(1.0);
            }
        }
    }

    private boolean updateZoomFactor() {
        Options options = OptionsManager.getInstance().getOptions();
        ZoomOptions zoomOptions = options.getEditorOptions().getZoomOptions();

        if (zoomOptions.isSmartZooming() && !zoomModel.isZoomLevelChanged()) {
            Double smartZoomFactor = getSmartZoomFactor(zoomOptions);
            if (smartZoomFactor != null) {
                zoomModel.setZoomFactor(smartZoomFactor);
                return true;
            }
        }
        return false;
    }

    private final class ImageContainerPane extends JBLayeredPane {
        private final ImageComponent imageComponent;

        public ImageContainerPane(final ImageComponent imageComponent) {
            this.imageComponent = imageComponent;
            add(imageComponent);

            putClientProperty(Magnificator.CLIENT_PROPERTY_KEY, new Magnificator() {
                @Override
                public Point magnify(double scale, Point at) {
                    Point locationBefore = imageComponent.getLocation();
                    ImageZoomModel model = editor != null ? editor.getZoomModel() : getZoomModel();
                    double factor = model.getZoomFactor();
                    model.setZoomFactor(scale * factor);
                    return new Point(((int) ((at.x - Math.max(scale > 1.0 ? locationBefore.x : 0, 0)) * scale)),
                            ((int) ((at.y - Math.max(scale > 1.0 ? locationBefore.y : 0, 0)) * scale)));
                }
            });
        }

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

        public void invalidate() {
            centerComponents();
            super.invalidate();
        }

        public Dimension getPreferredSize() {
            return imageComponent.getSize();
        }

        @Override
        protected void paintComponent(@Nonnull Graphics g) {
            super.paintComponent(g);
            if (UIUtil.isUnderDarcula()) {
                g.setColor(UIUtil.getControlColor().brighter());
                g.fillRect(0, 0, getWidth(), getHeight());
            }
        }
    }

    private final class ImageWheelAdapter implements MouseWheelListener {
        public void mouseWheelMoved(MouseWheelEvent e) {
            Options options = OptionsManager.getInstance().getOptions();
            EditorOptions editorOptions = options.getEditorOptions();
            ZoomOptions zoomOptions = editorOptions.getZoomOptions();
            if (zoomOptions.isWheelZooming() && e.isControlDown()) {
                int rotation = e.getWheelRotation();
                double oldZoomFactor = zoomModel.getZoomFactor();
                Point oldPosition = myScrollPane.getViewport().getViewPosition();

                if (rotation < 0) {
                    zoomModel.zoomOut();
                } else if (rotation > 0) {
                    zoomModel.zoomIn();
                }

                // reset view, otherwise view size is not obtained correctly sometimes
                Component view = myScrollPane.getViewport().getView();
                myScrollPane.setViewport(null);
                myScrollPane.setViewportView(view);

                if (oldZoomFactor > 0 && rotation != 0) {
                    Point mousePoint = e.getPoint();
                    double zoomChange = zoomModel.getZoomFactor() / oldZoomFactor;
                    Point newPosition = new Point(
                            (int) Math.max(0,
                                    (oldPosition.getX() + mousePoint.getX()) * zoomChange - mousePoint.getX()),
                            (int) Math.max(0,
                                    (oldPosition.getY() + mousePoint.getY()) * zoomChange - mousePoint.getY()));
                    myScrollPane.getViewport().setViewPosition(newPosition);
                }

                e.consume();
            }
        }
    }

    private class ImageZoomModelImpl implements ImageZoomModel {
        private boolean myZoomLevelChanged;
        private final NotNullValue<Double> IMAGE_MAX_ZOOM_FACTOR = new NotNullValue<Double>() {
            @Nonnull
            @Override
            public Double initialize() {
                if (editor == null) {
                    return Double.MAX_VALUE;
                }
                VirtualFile file = editor.getFile();

                if (IfsUtil.isSVG(file)) {
                    try {
                        URL url = new File(file.getPath()).toURI().toURL();
                        return Math.max(1,
                                SVGLoader.getMaxZoomFactor(url,
                                        new ByteArrayInputStream(file.contentsToByteArray()),
                                        ScaleContext.create(editor.getComponent())));
                    } catch (Throwable t) {
                        Logger.getInstance("#org.intellij.images.editor.impl.ImageEditorUI").warn(t);
                    }
                }
                return Double.MAX_VALUE;
            }
        };
        private double zoomFactor = 0.0d;

        public double getZoomFactor() {
            return zoomFactor;
        }

        public void setZoomFactor(double zoomFactor) {
            double oldZoomFactor = getZoomFactor();

            if (Double.compare(oldZoomFactor, zoomFactor) == 0) {
                return;
            }
            this.zoomFactor = zoomFactor;

            // Change current size
            updateImageComponentSize();

            revalidate();
            repaint();
            myZoomLevelChanged = false;

            imageComponent.firePropertyChange(ZOOM_FACTOR_PROP, oldZoomFactor, zoomFactor);
        }

        private double getMaximumZoomFactor() {
            double factor = IMAGE_MAX_ZOOM_FACTOR.get();
            return Math.min(factor, MACRO_ZOOM_LIMIT);
        }

        private double getMinimumZoomFactor() {
            Rectangle bounds = imageComponent.getDocument().getBounds();
            double factor = bounds != null ? 1.0d / bounds.getWidth() : 0.0d;
            return Math.max(factor, MICRO_ZOOM_LIMIT);
        }

        public void fitZoomToWindow() {
            Options options = OptionsManager.getInstance().getOptions();
            ZoomOptions zoomOptions = options.getEditorOptions().getZoomOptions();

            Double smartZoomFactor = getSmartZoomFactor(zoomOptions);
            if (smartZoomFactor != null) {
                zoomModel.setZoomFactor(smartZoomFactor);
            } else {
                zoomModel.setZoomFactor(1.0d);
            }
            myZoomLevelChanged = false;
        }

        public void zoomOut() {
            setZoomFactor(getNextZoomOut());
            myZoomLevelChanged = true;
        }

        public void zoomIn() {
            setZoomFactor(getNextZoomIn());
            myZoomLevelChanged = true;
        }

        private double getNextZoomOut() {
            double factor = getZoomFactor();
            if (factor > 1.0d) {
                // Macro
                factor /= MACRO_ZOOM_RATIO;
                factor = Math.max(factor, 1.0d);
            } else {
                // Micro
                factor /= MICRO_ZOOM_RATIO;
            }
            return Math.max(factor, getMinimumZoomFactor());
        }

        private double getNextZoomIn() {
            double factor = getZoomFactor();
            if (factor >= 1.0d) {
                // Macro
                factor *= MACRO_ZOOM_RATIO;
            } else {
                // Micro
                factor *= MICRO_ZOOM_RATIO;
                factor = Math.min(factor, 1.0d);
            }
            return Math.min(factor, getMaximumZoomFactor());
        }

        public boolean canZoomOut() {
            // Ignore small differences caused by floating-point arithmetic.
            return getZoomFactor() - 1.0e-14 > getMinimumZoomFactor();
        }

        public boolean canZoomIn() {
            return getZoomFactor() < getMaximumZoomFactor();
        }

        @Override
        public void setZoomLevelChanged(boolean value) {
            myZoomLevelChanged = value;
        }

        public boolean isZoomLevelChanged() {
            return myZoomLevelChanged;
        }
    }

    @Nullable
    private Double getSmartZoomFactor(@Nonnull ZoomOptions zoomOptions) {
        Rectangle bounds = imageComponent.getDocument().getBounds();
        if (bounds == null) {
            return null;
        }
        if (bounds.getWidth() == 0 || bounds.getHeight() == 0) {
            return null;
        }
        int width = bounds.width;
        int height = bounds.height;

        Dimension preferredMinimumSize = zoomOptions.getPrefferedSize();
        if (width < preferredMinimumSize.width && height < preferredMinimumSize.height) {
            double factor = (preferredMinimumSize.getWidth() / (double) width
                    + preferredMinimumSize.getHeight() / (double) height) / 2.0d;
            return Math.ceil(factor);
        }

        Dimension canvasSize = myScrollPane.getViewport().getExtentSize();
        canvasSize.height -= ImageComponent.IMAGE_INSETS * 2;
        canvasSize.width -= ImageComponent.IMAGE_INSETS * 2;
        if (canvasSize.width <= 0 || canvasSize.height <= 0) {
            return null;
        }

        if (canvasSize.width < width || canvasSize.height < height) {
            return Math.min((double) canvasSize.height / height, (double) canvasSize.width / width);
        }

        return 1.0d;
    }

    private void updateImageComponentSize() {
        Rectangle bounds = imageComponent.getDocument().getBounds();
        if (bounds != null) {
            final double zoom = getZoomModel().getZoomFactor();
            imageComponent.setCanvasSize((int) Math.ceil(bounds.width * zoom),
                    (int) Math.ceil(bounds.height * zoom));
        }
    }

    private class DocumentChangeListener implements ChangeListener {
        public void stateChanged(@Nonnull ChangeEvent e) {
            updateImageComponentSize();

            ImageDocument document = imageComponent.getDocument();
            BufferedImage value = document.getValue();

            CardLayout layout = (CardLayout) contentPanel.getLayout();
            layout.show(contentPanel, value != null ? IMAGE_PANEL : ERROR_PANEL);

            updateInfo();

            revalidate();
            repaint();
        }
    }

    private class FocusRequester extends MouseAdapter {
        public void mousePressed(@Nonnull MouseEvent e) {
            IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(
                    () -> IdeFocusManager.getGlobalInstance().requestFocus(ImageEditorUI.this, true));
        }
    }

    private static final class EditorMouseAdapter extends PopupHandler {
        @Override
        public void invokePopup(Component comp, int x, int y) {
            // Single right click
            ActionManager actionManager = ActionManager.getInstance();
            ActionGroup actionGroup = (ActionGroup) actionManager.getAction(ImageEditorActions.GROUP_POPUP);
            ActionPopupMenu menu = actionManager.createActionPopupMenu(ImageEditorActions.ACTION_PLACE,
                    actionGroup);
            JPopupMenu popupMenu = menu.getComponent();
            popupMenu.pack();
            popupMenu.show(comp, x, y);
        }
    }

    @Nullable
    @Override
    public Object getData(@Nonnull Key<?> dataId) {
        if (CommonDataKeys.PROJECT == dataId) {
            return editor != null ? editor.getProject() : null;
        } else if (CommonDataKeys.VIRTUAL_FILE == dataId) {
            return editor != null ? editor.getFile() : null;
        } else if (CommonDataKeys.VIRTUAL_FILE_ARRAY == dataId) {
            return editor != null ? new VirtualFile[] { editor.getFile() } : VirtualFile.EMPTY_ARRAY;
        } else if (CommonDataKeys.PSI_FILE == dataId) {
            return findPsiFile();
        } else if (CommonDataKeys.PSI_ELEMENT == dataId) {
            return findPsiFile();
        } else if (LangDataKeys.PSI_ELEMENT_ARRAY == dataId) {
            PsiElement psi = findPsiFile();
            return psi != null ? new PsiElement[] { psi } : PsiElement.EMPTY_ARRAY;
        } else if (PlatformDataKeys.COPY_PROVIDER == dataId && copyPasteSupport != null) {
            return this;
        } else if (PlatformDataKeys.CUT_PROVIDER == dataId && copyPasteSupport != null) {
            return copyPasteSupport.getCutProvider();
        } else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER == dataId) {
            return deleteProvider;
        } else if (ImageComponentDecorator.DATA_KEY == dataId) {
            return editor != null ? editor : this;
        }

        return null;
    }

    @Nullable
    private PsiFile findPsiFile() {
        VirtualFile file = editor != null ? editor.getFile() : null;
        return file != null && file.isValid() ? PsiManager.getInstance(editor.getProject()).findFile(file) : null;
    }

    @Override
    public void performCopy(@Nonnull DataContext dataContext) {
        ImageDocument document = imageComponent.getDocument();
        BufferedImage image = document.getValue();
        CopyPasteManager.getInstance().setContents(new ImageTransferable(image));
    }

    @Override
    public boolean isCopyEnabled(@Nonnull DataContext dataContext) {
        return true;
    }

    @Override
    public boolean isCopyVisible(@Nonnull DataContext dataContext) {
        return true;
    }

    private static class ImageTransferable implements Transferable {
        private final BufferedImage myImage;

        public ImageTransferable(@Nonnull BufferedImage image) {
            myImage = image;
        }

        @Override
        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[] { DataFlavor.imageFlavor };
        }

        @Override
        public boolean isDataFlavorSupported(DataFlavor dataFlavor) {
            return DataFlavor.imageFlavor.equals(dataFlavor);
        }

        @Override
        public Object getTransferData(DataFlavor dataFlavor) throws UnsupportedFlavorException {
            if (!DataFlavor.imageFlavor.equals(dataFlavor)) {
                throw new UnsupportedFlavorException(dataFlavor);
            }
            return myImage;
        }
    }

    private class OptionsChangeListener implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent evt) {
            Options options = (Options) evt.getSource();
            EditorOptions editorOptions = options.getEditorOptions();
            TransparencyChessboardOptions chessboardOptions = editorOptions.getTransparencyChessboardOptions();
            GridOptions gridOptions = editorOptions.getGridOptions();

            imageComponent.setTransparencyChessboardCellSize(chessboardOptions.getCellSize());
            imageComponent.setTransparencyChessboardWhiteColor(chessboardOptions.getWhiteColor());
            imageComponent.setTransparencyChessboardBlankColor(chessboardOptions.getBlackColor());
            imageComponent.setGridLineZoomFactor(gridOptions.getLineZoomFactor());
            imageComponent.setGridLineSpan(gridOptions.getLineSpan());
            imageComponent.setGridLineColor(gridOptions.getLineColor());
        }
    }

}