2 * Copyright 2004-2005 Alexey Efimov
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org.intellij.images.editor.impl;
18 import com.intellij.ide.CopyPasteSupport;
19 import com.intellij.ide.CopyProvider;
20 import com.intellij.ide.DeleteProvider;
21 import com.intellij.ide.PsiActionSupportFactory;
22 import com.intellij.openapi.actionSystem.*;
23 import com.intellij.openapi.ide.CopyPasteManager;
24 import com.intellij.openapi.ui.Messages;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.psi.PsiElement;
28 import com.intellij.psi.PsiManager;
29 import com.intellij.ui.IdeBorderFactory;
30 import com.intellij.ui.PopupHandler;
31 import com.intellij.ui.ScrollPaneFactory;
32 import com.intellij.ui.components.JBLayeredPane;
33 import com.intellij.ui.components.Magnificator;
34 import com.intellij.util.ui.UIUtil;
35 import org.intellij.images.ImagesBundle;
36 import org.intellij.images.editor.ImageDocument;
37 import org.intellij.images.editor.ImageEditor;
38 import org.intellij.images.editor.ImageZoomModel;
39 import org.intellij.images.editor.actionSystem.ImageEditorActions;
40 import org.intellij.images.options.*;
41 import org.intellij.images.ui.ImageComponent;
42 import org.intellij.images.ui.ImageComponentDecorator;
43 import org.jetbrains.annotations.NonNls;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
48 import javax.swing.event.ChangeEvent;
49 import javax.swing.event.ChangeListener;
51 import java.awt.datatransfer.DataFlavor;
52 import java.awt.datatransfer.Transferable;
53 import java.awt.datatransfer.UnsupportedFlavorException;
54 import java.awt.event.MouseAdapter;
55 import java.awt.event.MouseEvent;
56 import java.awt.event.MouseWheelEvent;
57 import java.awt.event.MouseWheelListener;
58 import java.awt.image.BufferedImage;
59 import java.awt.image.ColorModel;
60 import java.io.IOException;
61 import java.util.Locale;
66 * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
68 final class ImageEditorUI extends JPanel implements DataProvider, CopyProvider {
70 private static final String IMAGE_PANEL = "image";
72 private static final String ERROR_PANEL = "error";
74 private final ImageEditor editor;
75 private final DeleteProvider deleteProvider;
76 private final CopyPasteSupport copyPasteSupport;
78 private final ImageZoomModel zoomModel = new ImageZoomModelImpl();
79 private final ImageWheelAdapter wheelAdapter = new ImageWheelAdapter();
80 private final ChangeListener changeListener = new DocumentChangeListener();
81 private final ImageComponent imageComponent = new ImageComponent();
82 private final JPanel contentPanel;
83 private final JLabel infoLabel;
85 ImageEditorUI(ImageEditor editor, EditorOptions editorOptions) {
87 final PsiActionSupportFactory factory = PsiActionSupportFactory.getInstance();
88 if (factory != null) {
90 factory.createPsiBasedCopyPasteSupport(editor.getProject(), this, new PsiActionSupportFactory.PsiElementSelector() {
91 public PsiElement[] getSelectedElements() {
92 PsiElement[] data = LangDataKeys.PSI_ELEMENT_ARRAY.getData(ImageEditorUI.this);
93 return data == null ? PsiElement.EMPTY_ARRAY : data;
97 copyPasteSupport = null;
100 deleteProvider = factory == null ? null : factory.createPsiBasedDeleteProvider();
102 ImageDocument document = imageComponent.getDocument();
103 document.addChangeListener(changeListener);
106 TransparencyChessboardOptions chessboardOptions = editorOptions.getTransparencyChessboardOptions();
107 GridOptions gridOptions = editorOptions.getGridOptions();
108 imageComponent.setTransparencyChessboardCellSize(chessboardOptions.getCellSize());
109 imageComponent.setTransparencyChessboardWhiteColor(chessboardOptions.getWhiteColor());
110 imageComponent.setTransparencyChessboardBlankColor(chessboardOptions.getBlackColor());
111 imageComponent.setGridLineZoomFactor(gridOptions.getLineZoomFactor());
112 imageComponent.setGridLineSpan(gridOptions.getLineSpan());
113 imageComponent.setGridLineColor(gridOptions.getLineColor());
116 ImageContainerPane view = new ImageContainerPane(imageComponent);
117 view.addMouseListener(new EditorMouseAdapter());
118 view.addMouseListener(new FocusRequester());
120 JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(view);
121 scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
122 scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
124 // Zoom by wheel listener
125 scrollPane.addMouseWheelListener(wheelAdapter);
128 setLayout(new BorderLayout());
130 ActionManager actionManager = ActionManager.getInstance();
131 ActionGroup actionGroup = (ActionGroup)actionManager.getAction(ImageEditorActions.GROUP_TOOLBAR);
132 ActionToolbar actionToolbar = actionManager.createActionToolbar(
133 ImageEditorActions.ACTION_PLACE, actionGroup, true
135 actionToolbar.setTargetComponent(this);
137 JComponent toolbarPanel = actionToolbar.getComponent();
138 toolbarPanel.addMouseListener(new FocusRequester());
140 JLabel errorLabel = new JLabel(
141 ImagesBundle.message("error.broken.image.file.format"),
142 Messages.getErrorIcon(), SwingConstants.CENTER
145 JPanel errorPanel = new JPanel(new BorderLayout());
146 errorPanel.add(errorLabel, BorderLayout.CENTER);
148 contentPanel = new JPanel(new CardLayout());
149 contentPanel.add(scrollPane, IMAGE_PANEL);
150 contentPanel.add(errorPanel, ERROR_PANEL);
152 JPanel topPanel = new JPanel(new BorderLayout());
153 topPanel.add(toolbarPanel, BorderLayout.WEST);
154 infoLabel = new JLabel((String)null, SwingConstants.RIGHT);
155 infoLabel.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 0, 2));
156 topPanel.add(infoLabel, BorderLayout.EAST);
158 add(topPanel, BorderLayout.NORTH);
159 add(contentPanel, BorderLayout.CENTER);
164 private void updateInfo() {
165 ImageDocument document = imageComponent.getDocument();
166 BufferedImage image = document.getValue();
168 ColorModel colorModel = image.getColorModel();
169 String format = document.getFormat();
170 if (format == null) {
171 format = ImagesBundle.message("unknown.format");
173 format = format.toUpperCase(Locale.ENGLISH);
175 VirtualFile file = editor.getFile();
177 ImagesBundle.message("image.info",
178 image.getWidth(), image.getHeight(), format,
179 colorModel.getPixelSize(), file != null ? StringUtil.formatFileSize(file.getLength()) : ""));
181 infoLabel.setText(null);
185 @SuppressWarnings("UnusedDeclaration")
186 JComponent getContentComponent() {
190 ImageComponent getImageComponent() {
191 return imageComponent;
195 imageComponent.removeMouseWheelListener(wheelAdapter);
196 imageComponent.getDocument().removeChangeListener(changeListener);
201 ImageZoomModel getZoomModel() {
205 private final class ImageContainerPane extends JBLayeredPane {
206 private final ImageComponent imageComponent;
208 public ImageContainerPane(final ImageComponent imageComponent) {
209 this.imageComponent = imageComponent;
212 putClientProperty(Magnificator.CLIENT_PROPERTY_KEY, new Magnificator() {
214 public Point magnify(double scale, Point at) {
215 Point locationBefore = imageComponent.getLocation();
216 ImageZoomModel model = editor.getZoomModel();
217 double factor = model.getZoomFactor();
218 model.setZoomFactor(scale * factor);
219 return new Point(((int)((at.x - Math.max(scale > 1.0 ? locationBefore.x : 0, 0)) * scale)),
220 ((int)((at.y - Math.max(scale > 1.0 ? locationBefore.y : 0, 0)) * scale)));
225 private void centerComponents() {
226 Rectangle bounds = getBounds();
227 Point point = imageComponent.getLocation();
228 point.x = (bounds.width - imageComponent.getWidth()) / 2;
229 point.y = (bounds.height - imageComponent.getHeight()) / 2;
230 imageComponent.setLocation(point);
233 public void invalidate() {
238 public Dimension getPreferredSize() {
239 return imageComponent.getSize();
243 protected void paintComponent(@NotNull Graphics g) {
244 super.paintComponent(g);
245 if (UIUtil.isUnderDarcula()) {
246 g.setColor(UIUtil.getControlColor().brighter());
247 g.fillRect(0, 0, getWidth(), getHeight());
252 private final class ImageWheelAdapter implements MouseWheelListener {
253 public void mouseWheelMoved(MouseWheelEvent e) {
254 Options options = OptionsManager.getInstance().getOptions();
255 EditorOptions editorOptions = options.getEditorOptions();
256 ZoomOptions zoomOptions = editorOptions.getZoomOptions();
257 if (zoomOptions.isWheelZooming() && e.isControlDown()) {
258 if (e.getWheelRotation() < 0) {
268 private class ImageZoomModelImpl implements ImageZoomModel {
269 private boolean myZoomLevelChanged = false;
271 public double getZoomFactor() {
272 Dimension size = imageComponent.getCanvasSize();
273 BufferedImage image = imageComponent.getDocument().getValue();
274 return image != null ? size.getWidth() / (double)image.getWidth() : 0.0d;
277 public void setZoomFactor(double zoomFactor) {
278 // Change current size
279 Dimension size = imageComponent.getCanvasSize();
280 BufferedImage image = imageComponent.getDocument().getValue();
282 size.setSize((double)image.getWidth() * zoomFactor, (double)image.getHeight() * zoomFactor);
283 imageComponent.setCanvasSize(size);
288 myZoomLevelChanged = false;
291 private double getMinimumZoomFactor() {
292 BufferedImage image = imageComponent.getDocument().getValue();
293 return image != null ? 1.0d / image.getWidth() : 0.0d;
296 public void zoomOut() {
297 double factor = getZoomFactor();
300 setZoomFactor(factor / 2.0d);
303 double minFactor = getMinimumZoomFactor();
304 double stepSize = (1.0d - minFactor) / MICRO_ZOOM_LIMIT;
305 int step = (int)Math.ceil((1.0d - factor) / stepSize);
307 setZoomFactor(1.0d - stepSize * (step + 1));
309 myZoomLevelChanged = true;
312 public void zoomIn() {
313 double factor = getZoomFactor();
314 if (factor >= 1.0d) {
316 setZoomFactor(factor * 2.0d);
319 double minFactor = getMinimumZoomFactor();
320 double stepSize = (1.0d - minFactor) / MICRO_ZOOM_LIMIT;
321 double step = (1.0d - factor) / stepSize;
323 setZoomFactor(1.0d - stepSize * (step - 1));
325 myZoomLevelChanged = true;
328 public boolean canZoomOut() {
329 double factor = getZoomFactor();
330 double minFactor = getMinimumZoomFactor();
331 double stepSize = (1.0 - minFactor) / MICRO_ZOOM_LIMIT;
332 double step = Math.ceil((1.0 - factor) / stepSize);
334 return step < MICRO_ZOOM_LIMIT;
337 public boolean canZoomIn() {
338 double zoomFactor = getZoomFactor();
339 return zoomFactor < MACRO_ZOOM_LIMIT;
342 public boolean isZoomLevelChanged() {
343 return myZoomLevelChanged;
347 private class DocumentChangeListener implements ChangeListener {
348 public void stateChanged(@NotNull ChangeEvent e) {
349 ImageDocument document = imageComponent.getDocument();
350 BufferedImage value = document.getValue();
352 CardLayout layout = (CardLayout)contentPanel.getLayout();
353 layout.show(contentPanel, value != null ? IMAGE_PANEL : ERROR_PANEL);
362 private class FocusRequester extends MouseAdapter {
363 public void mousePressed(@NotNull MouseEvent e) {
368 private static final class EditorMouseAdapter extends PopupHandler {
370 public void invokePopup(Component comp, int x, int y) {
371 // Single right click
372 ActionManager actionManager = ActionManager.getInstance();
373 ActionGroup actionGroup = (ActionGroup)actionManager.getAction(ImageEditorActions.GROUP_POPUP);
374 ActionPopupMenu menu = actionManager.createActionPopupMenu(ImageEditorActions.ACTION_PLACE, actionGroup);
375 JPopupMenu popupMenu = menu.getComponent();
377 popupMenu.show(comp, x, y);
383 public Object getData(String dataId) {
385 if (CommonDataKeys.PROJECT.is(dataId)) {
386 return editor.getProject();
387 } else if (CommonDataKeys.VIRTUAL_FILE.is(dataId)) {
388 return editor.getFile();
389 } else if (CommonDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
390 return new VirtualFile[]{editor.getFile()};
391 } else if (CommonDataKeys.PSI_FILE.is(dataId)) {
392 return getData(CommonDataKeys.PSI_ELEMENT.getName());
393 } else if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
394 VirtualFile file = editor.getFile();
395 return file != null && file.isValid() ? PsiManager.getInstance(editor.getProject()).findFile(file) : null;
396 } else if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
397 return new PsiElement[]{(PsiElement)getData(CommonDataKeys.PSI_ELEMENT.getName())};
398 } else if (PlatformDataKeys.COPY_PROVIDER.is(dataId) && copyPasteSupport != null) {
400 } else if (PlatformDataKeys.CUT_PROVIDER.is(dataId) && copyPasteSupport != null) {
401 return copyPasteSupport.getCutProvider();
402 } else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
403 return deleteProvider;
404 } else if (ImageComponentDecorator.DATA_KEY.is(dataId)) {
412 public void performCopy(@NotNull DataContext dataContext) {
413 ImageDocument document = imageComponent.getDocument();
414 BufferedImage image = document.getValue();
415 CopyPasteManager.getInstance().setContents(new ImageTransferable(image));
419 public boolean isCopyEnabled(@NotNull DataContext dataContext) {
424 public boolean isCopyVisible(@NotNull DataContext dataContext) {
428 private static class ImageTransferable implements Transferable {
429 private final BufferedImage myImage;
431 public ImageTransferable(@NotNull BufferedImage image) {
436 public DataFlavor[] getTransferDataFlavors() {
437 return new DataFlavor[] { DataFlavor.imageFlavor };
441 public boolean isDataFlavorSupported(DataFlavor dataFlavor) {
442 return DataFlavor.imageFlavor.equals(dataFlavor);
446 public Object getTransferData(DataFlavor dataFlavor) throws UnsupportedFlavorException, IOException {
447 if (!DataFlavor.imageFlavor.equals(dataFlavor)) {
448 throw new UnsupportedFlavorException(dataFlavor);