| /* |
| * 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 com.intellij.ide.CopyPasteSupport; |
| import com.intellij.ide.CopyProvider; |
| import com.intellij.ide.DeleteProvider; |
| import com.intellij.ide.PsiActionSupportFactory; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.ide.CopyPasteManager; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiManager; |
| import com.intellij.ui.IdeBorderFactory; |
| 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.ui.UIUtil; |
| import org.intellij.images.ImagesBundle; |
| import org.intellij.images.editor.ImageDocument; |
| import org.intellij.images.editor.ImageEditor; |
| import org.intellij.images.editor.ImageZoomModel; |
| import org.intellij.images.editor.actionSystem.ImageEditorActions; |
| import org.intellij.images.options.*; |
| import org.intellij.images.ui.ImageComponent; |
| import org.intellij.images.ui.ImageComponentDecorator; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.ChangeEvent; |
| import javax.swing.event.ChangeListener; |
| import java.awt.*; |
| import java.awt.datatransfer.DataFlavor; |
| import java.awt.datatransfer.Transferable; |
| import java.awt.datatransfer.UnsupportedFlavorException; |
| 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.io.IOException; |
| import java.util.Locale; |
| |
| /** |
| * Image editor UI |
| * |
| * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a> |
| */ |
| final class ImageEditorUI extends JPanel implements DataProvider, CopyProvider { |
| @NonNls |
| private static final String IMAGE_PANEL = "image"; |
| @NonNls |
| private static final String ERROR_PANEL = "error"; |
| |
| 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; |
| |
| ImageEditorUI(ImageEditor editor, EditorOptions editorOptions) { |
| this.editor = editor; |
| final PsiActionSupportFactory factory = PsiActionSupportFactory.getInstance(); |
| if (factory != null) { |
| copyPasteSupport = |
| factory.createPsiBasedCopyPasteSupport(editor.getProject(), this, new PsiActionSupportFactory.PsiElementSelector() { |
| public PsiElement[] getSelectedElements() { |
| PsiElement[] data = LangDataKeys.PSI_ELEMENT_ARRAY.getData(ImageEditorUI.this); |
| return data == null ? PsiElement.EMPTY_ARRAY : data; |
| } |
| }); |
| } else { |
| copyPasteSupport = null; |
| } |
| |
| deleteProvider = factory == null ? null : factory.createPsiBasedDeleteProvider(); |
| |
| 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()); |
| |
| JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(view); |
| scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); |
| scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); |
| |
| // Zoom by wheel listener |
| scrollPane.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 |
| ); |
| 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(scrollPane, 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(IdeBorderFactory.createEmptyBorder(0, 0, 0, 2)); |
| topPanel.add(infoLabel, BorderLayout.EAST); |
| |
| add(topPanel, BorderLayout.NORTH); |
| add(contentPanel, BorderLayout.CENTER); |
| |
| 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 = ImagesBundle.message("unknown.format"); |
| } else { |
| format = format.toUpperCase(Locale.ENGLISH); |
| } |
| VirtualFile file = editor.getFile(); |
| 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; |
| } |
| |
| void dispose() { |
| imageComponent.removeMouseWheelListener(wheelAdapter); |
| imageComponent.getDocument().removeChangeListener(changeListener); |
| |
| removeAll(); |
| } |
| |
| ImageZoomModel getZoomModel() { |
| return zoomModel; |
| } |
| |
| 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.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(@NotNull 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()) { |
| if (e.getWheelRotation() < 0) { |
| zoomModel.zoomOut(); |
| } else { |
| zoomModel.zoomIn(); |
| } |
| e.consume(); |
| } |
| } |
| } |
| |
| private class ImageZoomModelImpl implements ImageZoomModel { |
| private boolean myZoomLevelChanged = false; |
| |
| public double getZoomFactor() { |
| Dimension size = imageComponent.getCanvasSize(); |
| BufferedImage image = imageComponent.getDocument().getValue(); |
| return image != null ? size.getWidth() / (double)image.getWidth() : 0.0d; |
| } |
| |
| public void setZoomFactor(double zoomFactor) { |
| // Change current size |
| Dimension size = imageComponent.getCanvasSize(); |
| BufferedImage image = imageComponent.getDocument().getValue(); |
| if (image != null) { |
| size.setSize((double)image.getWidth() * zoomFactor, (double)image.getHeight() * zoomFactor); |
| imageComponent.setCanvasSize(size); |
| } |
| |
| revalidate(); |
| repaint(); |
| myZoomLevelChanged = false; |
| } |
| |
| private double getMinimumZoomFactor() { |
| BufferedImage image = imageComponent.getDocument().getValue(); |
| return image != null ? 1.0d / image.getWidth() : 0.0d; |
| } |
| |
| public void zoomOut() { |
| double factor = getZoomFactor(); |
| if (factor > 1.0d) { |
| // Macro |
| setZoomFactor(factor / 2.0d); |
| } else { |
| // Micro |
| double minFactor = getMinimumZoomFactor(); |
| double stepSize = (1.0d - minFactor) / MICRO_ZOOM_LIMIT; |
| int step = (int)Math.ceil((1.0d - factor) / stepSize); |
| |
| setZoomFactor(1.0d - stepSize * (step + 1)); |
| } |
| myZoomLevelChanged = true; |
| } |
| |
| public void zoomIn() { |
| double factor = getZoomFactor(); |
| if (factor >= 1.0d) { |
| // Macro |
| setZoomFactor(factor * 2.0d); |
| } else { |
| // Micro |
| double minFactor = getMinimumZoomFactor(); |
| double stepSize = (1.0d - minFactor) / MICRO_ZOOM_LIMIT; |
| double step = (1.0d - factor) / stepSize; |
| |
| setZoomFactor(1.0d - stepSize * (step - 1)); |
| } |
| myZoomLevelChanged = true; |
| } |
| |
| public boolean canZoomOut() { |
| double factor = getZoomFactor(); |
| double minFactor = getMinimumZoomFactor(); |
| double stepSize = (1.0 - minFactor) / MICRO_ZOOM_LIMIT; |
| double step = Math.ceil((1.0 - factor) / stepSize); |
| |
| return step < MICRO_ZOOM_LIMIT; |
| } |
| |
| public boolean canZoomIn() { |
| double zoomFactor = getZoomFactor(); |
| return zoomFactor < MACRO_ZOOM_LIMIT; |
| } |
| |
| public boolean isZoomLevelChanged() { |
| return myZoomLevelChanged; |
| } |
| } |
| |
| private class DocumentChangeListener implements ChangeListener { |
| public void stateChanged(@NotNull ChangeEvent e) { |
| 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(@NotNull MouseEvent e) { |
| requestFocus(); |
| } |
| } |
| |
| 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 |
| public Object getData(String dataId) { |
| |
| if (CommonDataKeys.PROJECT.is(dataId)) { |
| return editor.getProject(); |
| } else if (CommonDataKeys.VIRTUAL_FILE.is(dataId)) { |
| return editor.getFile(); |
| } else if (CommonDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) { |
| return new VirtualFile[]{editor.getFile()}; |
| } else if (CommonDataKeys.PSI_FILE.is(dataId)) { |
| return getData(CommonDataKeys.PSI_ELEMENT.getName()); |
| } else if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { |
| VirtualFile file = editor.getFile(); |
| return file != null && file.isValid() ? PsiManager.getInstance(editor.getProject()).findFile(file) : null; |
| } else if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { |
| return new PsiElement[]{(PsiElement)getData(CommonDataKeys.PSI_ELEMENT.getName())}; |
| } else if (PlatformDataKeys.COPY_PROVIDER.is(dataId) && copyPasteSupport != null) { |
| return this; |
| } else if (PlatformDataKeys.CUT_PROVIDER.is(dataId) && copyPasteSupport != null) { |
| return copyPasteSupport.getCutProvider(); |
| } else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) { |
| return deleteProvider; |
| } else if (ImageComponentDecorator.DATA_KEY.is(dataId)) { |
| return editor; |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public void performCopy(@NotNull DataContext dataContext) { |
| ImageDocument document = imageComponent.getDocument(); |
| BufferedImage image = document.getValue(); |
| CopyPasteManager.getInstance().setContents(new ImageTransferable(image)); |
| } |
| |
| @Override |
| public boolean isCopyEnabled(@NotNull DataContext dataContext) { |
| return true; |
| } |
| |
| @Override |
| public boolean isCopyVisible(@NotNull DataContext dataContext) { |
| return true; |
| } |
| |
| private static class ImageTransferable implements Transferable { |
| private final BufferedImage myImage; |
| |
| public ImageTransferable(@NotNull 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, IOException { |
| if (!DataFlavor.imageFlavor.equals(dataFlavor)) { |
| throw new UnsupportedFlavorException(dataFlavor); |
| } |
| return myImage; |
| } |
| } |
| } |