summaryrefslogtreecommitdiff
path: root/images
diff options
context:
space:
mode:
authorJean-Baptiste Queru <jbq@google.com>2013-01-08 11:11:20 -0800
committerJean-Baptiste Queru <jbq@google.com>2013-01-08 11:11:20 -0800
commitb56ea2a18f232d79481e778085fd64e8ae486fc3 (patch)
tree44e1f6eb4864a45033f865b74fe783e3d784dd6a /images
downloadidea-b56ea2a18f232d79481e778085fd64e8ae486fc3.tar.gz
Snapshot of commit d5ec1d5018ed24f1b4f32b1d09df6dbd7e2fc425
from branch master of git://git.jetbrains.org/idea/community.git
Diffstat (limited to 'images')
-rw-r--r--images/images.iml15
-rw-r--r--images/src/META-INF/ImagesPlugin.xml143
-rw-r--r--images/src/META-INF/services/javax.imageio.spi.ImageReaderSpi1
-rw-r--r--images/src/icons/ImagesIcons.java23
-rw-r--r--images/src/org/intellij/images/ImagesBundle.java50
-rw-r--r--images/src/org/intellij/images/ImagesBundle.properties40
-rw-r--r--images/src/org/intellij/images/actions/ColorPickerForImageAction.java29
-rw-r--r--images/src/org/intellij/images/actions/EditExternallyAction.java137
-rw-r--r--images/src/org/intellij/images/actions/ShowThumbnailsAction.java56
-rw-r--r--images/src/org/intellij/images/actions/ToggleTransparencyChessboardAction.java47
-rw-r--r--images/src/org/intellij/images/completion/ImageLookupInfoProvider.java51
-rw-r--r--images/src/org/intellij/images/editor/ImageDocument.java66
-rw-r--r--images/src/org/intellij/images/editor/ImageEditor.java67
-rw-r--r--images/src/org/intellij/images/editor/ImageFileEditor.java22
-rw-r--r--images/src/org/intellij/images/editor/ImageZoomModel.java41
-rw-r--r--images/src/org/intellij/images/editor/actionSystem/ImageEditorActionUtil.java71
-rw-r--r--images/src/org/intellij/images/editor/actionSystem/ImageEditorActions.java32
-rw-r--r--images/src/org/intellij/images/editor/actions/ActualSizeAction.java48
-rw-r--r--images/src/org/intellij/images/editor/actions/ToggleGridAction.java47
-rw-r--r--images/src/org/intellij/images/editor/actions/ZoomInAction.java47
-rw-r--r--images/src/org/intellij/images/editor/actions/ZoomOutAction.java47
-rw-r--r--images/src/org/intellij/images/editor/impl/ImageEditorImpl.java208
-rw-r--r--images/src/org/intellij/images/editor/impl/ImageEditorManagerImpl.java54
-rw-r--r--images/src/org/intellij/images/editor/impl/ImageEditorUI.java379
-rw-r--r--images/src/org/intellij/images/editor/impl/ImageFileEditorImpl.java136
-rw-r--r--images/src/org/intellij/images/editor/impl/ImageFileEditorProvider.java76
-rw-r--r--images/src/org/intellij/images/editor/impl/ImageFileEditorState.java93
-rw-r--r--images/src/org/intellij/images/fileTypes/ImageDocumentationProvider.java76
-rw-r--r--images/src/org/intellij/images/fileTypes/ImageFileTypeManager.java44
-rw-r--r--images/src/org/intellij/images/fileTypes/impl/ImageFileTypeManagerImpl.java92
-rw-r--r--images/src/org/intellij/images/icons/EditExternaly.pngbin0 -> 372 bytes
-rw-r--r--images/src/org/intellij/images/icons/ImagesFileType.pngbin0 -> 322 bytes
-rw-r--r--images/src/org/intellij/images/icons/ThumbnailBlank.pngbin0 -> 953 bytes
-rw-r--r--images/src/org/intellij/images/icons/ThumbnailDirectory.pngbin0 -> 3234 bytes
-rw-r--r--images/src/org/intellij/images/icons/ThumbnailToolWindow.pngbin0 -> 280 bytes
-rw-r--r--images/src/org/intellij/images/icons/ToggleGrid.pngbin0 -> 419 bytes
-rw-r--r--images/src/org/intellij/images/icons/ToggleTransparencyChessboard.pngbin0 -> 379 bytes
-rw-r--r--images/src/org/intellij/images/index/ImageInfoIndex.java146
-rw-r--r--images/src/org/intellij/images/options/EditorOptions.java33
-rw-r--r--images/src/org/intellij/images/options/ExternalEditorOptions.java39
-rw-r--r--images/src/org/intellij/images/options/GridOptions.java55
-rw-r--r--images/src/org/intellij/images/options/Options.java53
-rw-r--r--images/src/org/intellij/images/options/OptionsManager.java36
-rw-r--r--images/src/org/intellij/images/options/TransparencyChessboardOptions.java54
-rw-r--r--images/src/org/intellij/images/options/ZoomOptions.java50
-rw-r--r--images/src/org/intellij/images/options/impl/EditorOptionsImpl.java108
-rw-r--r--images/src/org/intellij/images/options/impl/ExternalEditorOptionsImpl.java97
-rw-r--r--images/src/org/intellij/images/options/impl/GridOptionsImpl.java152
-rw-r--r--images/src/org/intellij/images/options/impl/JDOMExternalizerEx.java46
-rw-r--r--images/src/org/intellij/images/options/impl/Options.form234
-rw-r--r--images/src/org/intellij/images/options/impl/OptionsConfigurabe.java109
-rw-r--r--images/src/org/intellij/images/options/impl/OptionsImpl.java112
-rw-r--r--images/src/org/intellij/images/options/impl/OptionsManagerImpl.java66
-rw-r--r--images/src/org/intellij/images/options/impl/OptionsUIForm.java261
-rw-r--r--images/src/org/intellij/images/options/impl/TransparencyChessboardOptionsImpl.java161
-rw-r--r--images/src/org/intellij/images/options/impl/ZoomOptionsImpl.java155
-rw-r--r--images/src/org/intellij/images/thumbnail/ThumbnailManager.java41
-rw-r--r--images/src/org/intellij/images/thumbnail/ThumbnailView.java75
-rw-r--r--images/src/org/intellij/images/thumbnail/actionSystem/ThumbnailViewActionUtil.java61
-rw-r--r--images/src/org/intellij/images/thumbnail/actionSystem/ThumbnailViewActions.java29
-rw-r--r--images/src/org/intellij/images/thumbnail/actions/EnterAction.java78
-rw-r--r--images/src/org/intellij/images/thumbnail/actions/HideThumbnailsAction.java43
-rw-r--r--images/src/org/intellij/images/thumbnail/actions/ToggleRecursiveAction.java48
-rw-r--r--images/src/org/intellij/images/thumbnail/actions/UpFolderAction.java54
-rw-r--r--images/src/org/intellij/images/thumbnail/impl/ThumbnailManagerImpl.java51
-rw-r--r--images/src/org/intellij/images/thumbnail/impl/ThumbnailSelectInTarget.java63
-rw-r--r--images/src/org/intellij/images/thumbnail/impl/ThumbnailViewImpl.java178
-rw-r--r--images/src/org/intellij/images/thumbnail/impl/ThumbnailViewUI.java586
-rw-r--r--images/src/org/intellij/images/ui/ImageComponent.java319
-rw-r--r--images/src/org/intellij/images/ui/ImageComponentDecorator.java39
-rw-r--r--images/src/org/intellij/images/ui/ImageComponentUI.java118
-rw-r--r--images/src/org/intellij/images/ui/ThumbnailComponent.java144
-rw-r--r--images/src/org/intellij/images/ui/ThumbnailComponentUI.java288
-rw-r--r--images/src/org/intellij/images/util/ImageInfoReader.java248
-rw-r--r--images/src/org/intellij/images/util/imageio/SanselanImageReaderSpi.java245
-rw-r--r--images/src/org/intellij/images/vfs/IfsUtil.java151
76 files changed, 7064 insertions, 0 deletions
diff --git a/images/images.iml b/images/images.iml
new file mode 100644
index 000000000000..5a3f83d41703
--- /dev/null
+++ b/images/images.iml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="lang-api" />
+ <orderEntry type="module" module-name="lang-impl" />
+ <orderEntry type="library" name="Sanselan" level="project" />
+ </component>
+</module>
+
diff --git a/images/src/META-INF/ImagesPlugin.xml b/images/src/META-INF/ImagesPlugin.xml
new file mode 100644
index 000000000000..b420cdba3076
--- /dev/null
+++ b/images/src/META-INF/ImagesPlugin.xml
@@ -0,0 +1,143 @@
+<idea-plugin version="2">
+
+ <vendor>JetBrains</vendor>
+
+ <extensions defaultExtensionNs="com.intellij">
+ <errorHandler implementation="com.intellij.diagnostic.ITNReporter"/>
+ <applicationConfigurable instance="org.intellij.images.options.impl.OptionsConfigurabe" id="Images" displayName="Images"/>
+ <fileEditorProvider implementation="org.intellij.images.editor.impl.ImageFileEditorProvider"/>
+ <selectInTarget implementation="org.intellij.images.thumbnail.impl.ThumbnailSelectInTarget"/>
+ <applicationService serviceInterface="org.intellij.images.options.OptionsManager"
+ serviceImplementation="org.intellij.images.options.impl.OptionsManagerImpl"/>
+ <projectService serviceInterface="org.intellij.images.thumbnail.ThumbnailManager"
+ serviceImplementation="org.intellij.images.thumbnail.impl.ThumbnailManagerImpl"/>
+ <fileTypeFactory implementation="org.intellij.images.fileTypes.impl.ImageFileTypeManagerImpl" />
+ <fileBasedIndex implementation="org.intellij.images.index.ImageInfoIndex"/>
+ <fileLookupInfoProvider implementation="org.intellij.images.completion.ImageLookupInfoProvider"/>
+ <documentationProvider implementation="org.intellij.images.fileTypes.ImageDocumentationProvider"/>
+ </extensions>
+
+ <application-components>
+ <component>
+ <interface-class>org.intellij.images.fileTypes.ImageFileTypeManager</interface-class>
+ <implementation-class>org.intellij.images.fileTypes.impl.ImageFileTypeManagerImpl</implementation-class>
+ </component>
+ </application-components>
+
+ <actions>
+ <action id="images.color.picker" class="org.intellij.images.actions.ColorPickerForImageAction" text="Show Color Picker">
+ <add-to-group anchor="after" group-id="ProjectViewPopupMenu" relative-to-action="EditSource"/>
+ </action>
+ <action class="org.intellij.images.actions.EditExternallyAction"
+ id="Images.EditExternaly"
+ icon="ImagesIcons.EditExternaly"
+ text="Open Image in External Editor">
+ <keyboard-shortcut first-keystroke="control alt F4" keymap="$default"/>
+ <add-to-group anchor="after" group-id="ProjectViewPopupMenu" relative-to-action="EditSource"/>
+ </action>
+ <action class="org.intellij.images.actions.ShowThumbnailsAction"
+ id="Images.ShowThumbnails" text="Show Image Thumbnails">
+ <keyboard-shortcut first-keystroke="shift control T" keymap="$default"/>
+ <add-to-group anchor="after" group-id="ProjectViewPopupMenu" relative-to-action="AddToFavorites"/>
+ <add-to-group anchor="after" group-id="NavbarPopupMenu" relative-to-action="AddToFavorites"/>
+ </action>
+ <action class="org.intellij.images.actions.ToggleTransparencyChessboardAction"
+ id="Images.ToggleTransparencyChessboard"
+ icon="ImagesIcons.ToggleTransparencyChessboard"
+ text="Show Chessboard"
+ description="Show a chessboard on transparent image parts">
+ </action>
+ <group id="Images.EditorToolbar">
+ <reference id="Images.ToggleTransparencyChessboard"/>
+ <action class="org.intellij.images.editor.actions.ToggleGridAction"
+ id="Images.Editor.ToggleGrid"
+ icon="ImagesIcons.ToggleGrid"
+ text="Show Grid">
+ <keyboard-shortcut first-keystroke="control QUOTE" keymap="$default"/>
+ </action>
+ <separator/>
+ <action class="org.intellij.images.editor.actions.ZoomInAction"
+ id="Images.Editor.ZoomIn"
+ icon="AllIcons.Graph.ZoomIn"
+ text="Zoom In"
+ use-shortcut-of="ExpandAll" />
+ <action class="org.intellij.images.editor.actions.ZoomOutAction"
+ id="Images.Editor.ZoomOut"
+ icon="AllIcons.Graph.ZoomOut"
+ text="Zoom Out"
+ use-shortcut-of="CollapseAll"/>
+ <action class="org.intellij.images.editor.actions.ActualSizeAction"
+ id="Images.Editor.ActualSize"
+ icon="AllIcons.Graph.ActualZoom"
+ text="Zoom to Actual Size">
+ <keyboard-shortcut first-keystroke="control DIVIDE" keymap="$default"/>
+ <keyboard-shortcut first-keystroke="control SLASH" keymap="$default"/>
+ </action>
+ </group>
+ <group id="Images.EditorPopupMenu">
+ <reference id="CutCopyPasteGroup"/>
+ <separator/>
+ <reference id="FindUsages"/>
+ <reference id="RefactoringMenu"/>
+ <separator/>
+ <reference id="Images.EditorToolbar"/>
+ <separator/>
+ <reference id="CloseEditor"/>
+ <separator/>
+ <reference id="AddToFavorites"/>
+ <separator/>
+ <reference id="VersionControlsGroup"/>
+ <separator/>
+ <reference id="images.color.picker" />
+ <reference id="Images.EditExternaly"/>
+ <reference id="ExternalToolsGroup"/>
+ </group>
+ <group id="Images.ThumbnailsToolbar">
+ <action class="org.intellij.images.thumbnail.actions.UpFolderAction"
+ id="Images.Thumbnails.UpFolder"
+ text="Parent Folder"
+ description="Show image thumbnails from the containing folder"
+ icon="AllIcons.Nodes.UpFolder">
+ <keyboard-shortcut first-keystroke="BACK_SPACE" keymap="$default"/>
+ </action>
+ <action class="org.intellij.images.thumbnail.actions.ToggleRecursiveAction"
+ id="Images.Thumbnails.ToggleRecursive"
+ text="Recursive"
+ description="Toggle whether to show the images from subfolders recursively"
+ icon="AllIcons.ObjectBrowser.FlattenPackages">
+ <keyboard-shortcut first-keystroke="control MULTIPLY" keymap="$default"/>
+ </action>
+ <separator/>
+ <reference id="Images.ToggleTransparencyChessboard"/>
+ <separator/>
+ <action class="org.intellij.images.thumbnail.actions.HideThumbnailsAction"
+ id="Images.Thumbnails.Hide"
+ text="Hide"
+ description="Hide image thumbnails"
+ icon="AllIcons.Actions.Cancel" use-shortcut-of="CloseContent"/>
+ </group>
+ <group id="Images.ThumbnailsPopupMenu">
+ <reference id="CutCopyPasteGroup"/>
+ <reference id="EditSource"/>
+ <action class="org.intellij.images.thumbnail.actions.EnterAction"
+ id="Images.Thumbnails.EnterAction">
+ <keyboard-shortcut first-keystroke="ENTER" keymap="$default"/>
+ </action>
+ <separator/>
+ <reference id="Images.ThumbnailsToolbar"/>
+ <separator/>
+ <reference id="FindUsages"/>
+ <reference id="RefactoringMenu"/>
+ <separator/>
+ <reference id="AddToFavorites"/>
+ <separator/>
+ <reference id="$Delete"/>
+ <separator/>
+ <reference id="VersionControlsGroup"/>
+ <reference id="CompareTwoFiles"/>
+ <separator/>
+ <reference id="Images.EditExternaly"/>
+ <reference id="ExternalToolsGroup"/>
+ </group>
+ </actions>
+</idea-plugin>
diff --git a/images/src/META-INF/services/javax.imageio.spi.ImageReaderSpi b/images/src/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100644
index 000000000000..85a34e519ea1
--- /dev/null
+++ b/images/src/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1 @@
+org.intellij.images.util.imageio.SanselanImageReaderSpi \ No newline at end of file
diff --git a/images/src/icons/ImagesIcons.java b/images/src/icons/ImagesIcons.java
new file mode 100644
index 000000000000..de9578ec6e17
--- /dev/null
+++ b/images/src/icons/ImagesIcons.java
@@ -0,0 +1,23 @@
+package icons;
+
+import com.intellij.openapi.util.IconLoader;
+
+import javax.swing.*;
+
+/**
+ * NOTE THIS FILE IS AUTO-GENERATED by the build/scripts/icons.gant
+ * Don't repeat mistakes of others ;-)
+ */
+public class ImagesIcons {
+ private static Icon load(String path) {
+ return IconLoader.getIcon(path, ImagesIcons.class);
+ }
+
+ public static final Icon EditExternaly = load("/org/intellij/images/icons/EditExternaly.png"); // 16x16
+ public static final Icon ImagesFileType = load("/org/intellij/images/icons/ImagesFileType.png"); // 16x16
+ public static final Icon ThumbnailBlank = load("/org/intellij/images/icons/ThumbnailBlank.png"); // 75x86
+ public static final Icon ThumbnailDirectory = load("/org/intellij/images/icons/ThumbnailDirectory.png"); // 75x82
+ public static final Icon ThumbnailToolWindow = load("/org/intellij/images/icons/ThumbnailToolWindow.png"); // 13x13
+ public static final Icon ToggleGrid = load("/org/intellij/images/icons/ToggleGrid.png"); // 16x16
+ public static final Icon ToggleTransparencyChessboard = load("/org/intellij/images/icons/ToggleTransparencyChessboard.png"); // 18x18
+}
diff --git a/images/src/org/intellij/images/ImagesBundle.java b/images/src/org/intellij/images/ImagesBundle.java
new file mode 100644
index 000000000000..b2a7092b1306
--- /dev/null
+++ b/images/src/org/intellij/images/ImagesBundle.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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;
+
+import com.intellij.CommonBundle;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.PropertyKey;
+
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.util.ResourceBundle;
+
+/**
+ * @author max
+ */
+public class ImagesBundle {
+ private static Reference<ResourceBundle> ourBundle;
+
+ @NonNls private static final String BUNDLE = "org.intellij.images.ImagesBundle";
+
+ private ImagesBundle() {
+ }
+
+ public static String message(@PropertyKey(resourceBundle = BUNDLE)String key, Object... params) {
+ return CommonBundle.message(getBundle(), key, params);
+ }
+
+ private static ResourceBundle getBundle() {
+ ResourceBundle bundle = null;
+ if (ourBundle != null) bundle = ourBundle.get();
+ if (bundle == null) {
+ bundle = ResourceBundle.getBundle(BUNDLE);
+ ourBundle = new SoftReference<ResourceBundle>(bundle);
+ }
+ return bundle;
+ }
+}
diff --git a/images/src/org/intellij/images/ImagesBundle.properties b/images/src/org/intellij/images/ImagesBundle.properties
new file mode 100644
index 000000000000..92638d0ce26f
--- /dev/null
+++ b/images/src/org/intellij/images/ImagesBundle.properties
@@ -0,0 +1,40 @@
+error.empty.external.editor.path=Please configure external editor executable path
+error.title.empty.external.editor.path=External Editor not Configured
+error.title.launching.external.editor=Problem launching external executable
+
+select.external.executable.title=Select editor
+select.external.executable.message=Select external graphics editor
+
+error.broken.image.file.format=<html><b>Image not loaded</b><br>Try to open it externally to fix format problem</html>
+images.filetype.description=Image files
+settings.page.name=Images
+
+thumbnails.toolwindow.name=Thumbnails
+thumbnails.component.error.text=Error
+
+icons.count={0,choice, 0#no images|1#1 image|2#{0,number} images|100# > 100 images}
+
+#widthXheightXcolor depth
+icon.dimensions={0,number}x{1,number}x{2,number}
+
+# settings page
+settings.preffered.smart.zoom.width=Preferred minimum wi&dth for smart zooming (pixels):
+settings.preffered.smart.zoom.height=Preferred minimum &height for smart zooming (pixels):
+show.grid.lines=Show &Grid lines by default
+show.transparency.chessboard=Show &transparency chessboard by default
+enable.mousewheel.zooming=Zoom image with &mouse wheel ({0}+Mouse Wheel)
+smart.zoom=Enable smart &zooming for small images
+chessboard.cell.size=Chessboard cell &size (pixels):
+show.grid.every=Show Grid line after &every (pixels):
+show.grid.zoom.limit=Show Grid lines &only then zoom factor equal or more than:
+white.cell.color=Color of '&white' cell:
+black.cell.color=Color of '&black' cell:
+grid.line.color=Grid line &color:
+external.editor.executable.path=Executable &path:
+external.editor.border.title=External Editor
+main.page.border.title=Editor
+
+plugin.Images.description=Provides image viewing and thumbnail browsing
+
+unknown.format=Unknown Format
+image.info={0}x{1} {2} ({3}-bit color) {4} \ No newline at end of file
diff --git a/images/src/org/intellij/images/actions/ColorPickerForImageAction.java b/images/src/org/intellij/images/actions/ColorPickerForImageAction.java
new file mode 100644
index 000000000000..ea70d4eb9cb8
--- /dev/null
+++ b/images/src/org/intellij/images/actions/ColorPickerForImageAction.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * 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.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.ui.ShowColorPickerAction;
+
+/**
+ * @author Konstantin Bulenkov
+ */
+public class ColorPickerForImageAction extends ShowColorPickerAction {
+ @Override
+ public void update(AnActionEvent e) {
+ EditExternallyAction.doUpdate(e);
+ }
+}
diff --git a/images/src/org/intellij/images/actions/EditExternallyAction.java b/images/src/org/intellij/images/actions/EditExternallyAction.java
new file mode 100644
index 000000000000..945edb5c2e85
--- /dev/null
+++ b/images/src/org/intellij/images/actions/EditExternallyAction.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * 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.actions;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.execution.util.ExecUtil;
+import com.intellij.openapi.actionSystem.ActionPlaces;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.EnvironmentUtil;
+import org.intellij.images.ImagesBundle;
+import org.intellij.images.fileTypes.ImageFileTypeManager;
+import org.intellij.images.options.Options;
+import org.intellij.images.options.OptionsManager;
+import org.intellij.images.options.impl.OptionsConfigurabe;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Open image file externally.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public final class EditExternallyAction extends AnAction {
+ public void actionPerformed(AnActionEvent e) {
+ Project project = e.getData(PlatformDataKeys.PROJECT);
+ VirtualFile[] files = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY);
+ Options options = OptionsManager.getInstance().getOptions();
+ String executablePath = options.getExternalEditorOptions().getExecutablePath();
+ if (StringUtil.isEmpty(executablePath)) {
+ Messages.showErrorDialog(project,
+ ImagesBundle.message("error.empty.external.editor.path"),
+ ImagesBundle.message("error.title.empty.external.editor.path"));
+ OptionsConfigurabe.show(project);
+ }
+ else {
+ if (files != null) {
+ Map<String, String> env = EnvironmentUtil.getEnvironmentProperties();
+ Set<String> varNames = env.keySet();
+ for (String varName : varNames) {
+ if (SystemInfo.isWindows) {
+ executablePath = StringUtil.replace(executablePath, "%" + varName + "%", env.get(varName), true);
+ }
+ else {
+ executablePath = StringUtil.replace(executablePath, "${" + varName + "}", env.get(varName), false);
+ }
+ }
+ executablePath = FileUtil.toSystemDependentName(executablePath);
+ File executable = new File(executablePath);
+ GeneralCommandLine commandLine = new GeneralCommandLine();
+ final String path = executable.exists() ? executable.getAbsolutePath() : executablePath;
+ if (SystemInfo.isMac) {
+ commandLine.setExePath(ExecUtil.getOpenCommandPath());
+ commandLine.addParameter("-a");
+ commandLine.addParameter(path);
+ } else {
+ commandLine.setExePath(path);
+ }
+
+ ImageFileTypeManager typeManager = ImageFileTypeManager.getInstance();
+ for (VirtualFile file : files) {
+ if (file.isInLocalFileSystem() && typeManager.isImage(file)) {
+ commandLine.addParameter(VfsUtil.virtualToIoFile(file).getAbsolutePath());
+ }
+ }
+ commandLine.setWorkDirectory(new File(executablePath).getParentFile());
+
+ try {
+ commandLine.createProcess();
+ }
+ catch (ExecutionException ex) {
+ Messages.showErrorDialog(project,
+ ex.getLocalizedMessage(),
+ ImagesBundle.message("error.title.launching.external.editor"));
+ OptionsConfigurabe.show(project);
+ }
+ }
+ }
+ }
+
+ public void update(AnActionEvent e) {
+ super.update(e);
+
+ doUpdate(e);
+ }
+
+ static void doUpdate(AnActionEvent e) {
+ VirtualFile[] files = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY);
+ final boolean isEnabled = isImages(files);
+ if (e.getPlace().equals(ActionPlaces.PROJECT_VIEW_POPUP)) {
+ e.getPresentation().setVisible(isEnabled);
+ }
+ else {
+ e.getPresentation().setEnabled(isEnabled);
+ }
+ }
+
+ private static boolean isImages(VirtualFile[] files) {
+ boolean isImagesFound = false;
+ if (files != null) {
+ ImageFileTypeManager typeManager = ImageFileTypeManager.getInstance();
+ for (VirtualFile file : files) {
+ boolean isImage = typeManager.isImage(file);
+ isImagesFound |= isImage;
+ if (!file.isInLocalFileSystem() || !isImage) {
+ return false;
+ }
+ }
+ }
+ return isImagesFound;
+ }
+}
diff --git a/images/src/org/intellij/images/actions/ShowThumbnailsAction.java b/images/src/org/intellij/images/actions/ShowThumbnailsAction.java
new file mode 100644
index 000000000000..70a8f113a4cf
--- /dev/null
+++ b/images/src/org/intellij/images/actions/ShowThumbnailsAction.java
@@ -0,0 +1,56 @@
+/*
+ * 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.actions;
+
+import com.intellij.openapi.actionSystem.ActionPlaces;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.intellij.images.thumbnail.ThumbnailManager;
+import org.intellij.images.thumbnail.ThumbnailView;
+
+/**
+ * Show thumbnail for directory.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public final class ShowThumbnailsAction extends AnAction {
+ public void actionPerformed(AnActionEvent e) {
+ Project project = e.getData(PlatformDataKeys.PROJECT);
+ VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE);
+ if (project != null && file != null && file.isDirectory()) {
+ ThumbnailManager thumbnailManager = ThumbnailManager.getManager(project);
+ ThumbnailView thumbnailView = thumbnailManager.getThumbnailView();
+ thumbnailView.setRoot(file);
+ thumbnailView.setVisible(true);
+ thumbnailView.activate();
+ }
+ }
+
+ public void update(AnActionEvent e) {
+ super.update(e);
+ VirtualFile file = e.getData(PlatformDataKeys.VIRTUAL_FILE);
+ final boolean isEnabled = file != null && file.isDirectory();
+ if (e.getPlace().equals(ActionPlaces.PROJECT_VIEW_POPUP)) {
+ e.getPresentation().setVisible(isEnabled);
+ }
+ else {
+ e.getPresentation().setEnabled(isEnabled);
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/actions/ToggleTransparencyChessboardAction.java b/images/src/org/intellij/images/actions/ToggleTransparencyChessboardAction.java
new file mode 100644
index 000000000000..07634679fc41
--- /dev/null
+++ b/images/src/org/intellij/images/actions/ToggleTransparencyChessboardAction.java
@@ -0,0 +1,47 @@
+/*
+ * 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.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.ToggleAction;
+import org.intellij.images.ui.ImageComponentDecorator;
+
+/**
+ * Show/hide background action.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ * @see org.intellij.images.ui.ImageComponentDecorator#setTransparencyChessboardVisible
+ */
+public final class ToggleTransparencyChessboardAction extends ToggleAction {
+ public boolean isSelected(AnActionEvent e) {
+ ImageComponentDecorator decorator = ImageComponentDecorator.DATA_KEY.getData(e.getDataContext());
+ return decorator != null && decorator.isEnabledForActionPlace(e.getPlace()) && decorator.isTransparencyChessboardVisible();
+ }
+
+ public void setSelected(AnActionEvent e, boolean state) {
+ ImageComponentDecorator decorator = ImageComponentDecorator.DATA_KEY.getData(e.getDataContext());
+ if (decorator != null && decorator.isEnabledForActionPlace(e.getPlace())) {
+ decorator.setTransparencyChessboardVisible(state);
+ }
+ }
+
+ public void update(final AnActionEvent e) {
+ super.update(e);
+ ImageComponentDecorator decorator = ImageComponentDecorator.DATA_KEY.getData(e.getDataContext());
+ e.getPresentation().setEnabled(decorator != null && decorator.isEnabledForActionPlace(e.getPlace()));
+ e.getPresentation().setText(isSelected(e) ? "Hide Chessboard" : "Show Chessboard");
+ }
+}
diff --git a/images/src/org/intellij/images/completion/ImageLookupInfoProvider.java b/images/src/org/intellij/images/completion/ImageLookupInfoProvider.java
new file mode 100644
index 000000000000..8cdda68d4e2b
--- /dev/null
+++ b/images/src/org/intellij/images/completion/ImageLookupInfoProvider.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.completion;
+
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.file.FileLookupInfoProvider;
+import com.intellij.util.indexing.FileBasedIndex;
+import org.intellij.images.fileTypes.ImageFileTypeManager;
+import org.intellij.images.index.ImageInfoIndex;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author spleaner
+ */
+public class ImageLookupInfoProvider extends FileLookupInfoProvider {
+
+ public Pair<String, String> getLookupInfo(@NotNull VirtualFile file, Project project) {
+ final String[] s = new String[] {null};
+ ImageInfoIndex.processValues(file, new FileBasedIndex.ValueProcessor<ImageInfoIndex.ImageInfo>() {
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ public boolean process(VirtualFile file, ImageInfoIndex.ImageInfo value) {
+ s[0] = String.format("%sx%s", value.width, value.height);
+ return true;
+ }
+ }, project);
+
+ return s[0] == null ? null : new Pair<String, String>(file.getName(), s[0]);
+ }
+
+ @NotNull
+ @Override
+ public FileType[] getFileTypes() {
+ return new FileType[]{ImageFileTypeManager.getInstance().getImageFileType()};
+ }
+}
diff --git a/images/src/org/intellij/images/editor/ImageDocument.java b/images/src/org/intellij/images/editor/ImageDocument.java
new file mode 100644
index 000000000000..3c39a8220a03
--- /dev/null
+++ b/images/src/org/intellij/images/editor/ImageDocument.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+/**
+ * Image document to show or edit in {@link ImageEditor}.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface ImageDocument {
+ /**
+ * Return image for rendering
+ *
+ * @return Image renderer
+ */
+ Image getRenderer();
+
+ /**
+ * Return current image.
+ *
+ * @return Return current buffered image
+ */
+ BufferedImage getValue();
+
+ /**
+ * Set image value
+ *
+ * @param image Value
+ */
+ void setValue(BufferedImage image);
+
+ /**
+ * Return image format.
+ *
+ * @return Format name
+ */
+ String getFormat();
+
+ /**
+ * Set image format.
+ *
+ * @param format Format from ImageIO (GIF, PNG, JPEG etc)
+ */
+ void setFormat(String format);
+
+ void addChangeListener(ChangeListener listener);
+
+ void removeChangeListener(ChangeListener listener);
+}
diff --git a/images/src/org/intellij/images/editor/ImageEditor.java b/images/src/org/intellij/images/editor/ImageEditor.java
new file mode 100644
index 000000000000..a888961b8af0
--- /dev/null
+++ b/images/src/org/intellij/images/editor/ImageEditor.java
@@ -0,0 +1,67 @@
+/*
+* 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;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileListener;
+import org.intellij.images.ui.ImageComponentDecorator;
+
+import javax.swing.*;
+
+/**
+ * Image viewer.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface ImageEditor extends Disposable, VirtualFileListener, ImageComponentDecorator {
+ VirtualFile getFile();
+
+ Project getProject();
+
+ ImageDocument getDocument();
+
+ JComponent getComponent();
+
+ /**
+ * Return the target of image editing area within entire component,
+ * returned by {@link #getComponent()}.
+ *
+ * @return Content component
+ */
+ JComponent getContentComponent();
+
+ /**
+ * Return <code>true</code> if editor show valid image.
+ *
+ * @return <code>true</code> if editor show valid image.
+ */
+ boolean isValid();
+
+ /**
+ * Return <code>true</code> if editor is already disposed.
+ *
+ * @return <code>true</code> if editor is already disposed.
+ */
+ boolean isDisposed();
+
+ ImageZoomModel getZoomModel();
+
+ void setGridVisible(boolean visible);
+
+ boolean isGridVisible();
+}
diff --git a/images/src/org/intellij/images/editor/ImageFileEditor.java b/images/src/org/intellij/images/editor/ImageFileEditor.java
new file mode 100644
index 000000000000..27c3ddbf9730
--- /dev/null
+++ b/images/src/org/intellij/images/editor/ImageFileEditor.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+import com.intellij.openapi.fileEditor.FileEditor;
+
+public interface ImageFileEditor extends FileEditor {
+ ImageEditor getImageEditor();
+}
diff --git a/images/src/org/intellij/images/editor/ImageZoomModel.java b/images/src/org/intellij/images/editor/ImageZoomModel.java
new file mode 100644
index 000000000000..a8902fd9aa21
--- /dev/null
+++ b/images/src/org/intellij/images/editor/ImageZoomModel.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Location model presents bounds of image.
+ * The zoom it calculated as y = exp(x/2).
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface ImageZoomModel {
+ int MACRO_ZOOM_LIMIT = 32;
+ int MICRO_ZOOM_LIMIT = 8;
+
+ double getZoomFactor();
+
+ void setZoomFactor(double zoomFactor);
+
+ void zoomOut();
+
+ void zoomIn();
+
+ boolean canZoomOut();
+
+ boolean canZoomIn();
+
+ boolean isZoomLevelChanged();
+}
diff --git a/images/src/org/intellij/images/editor/actionSystem/ImageEditorActionUtil.java b/images/src/org/intellij/images/editor/actionSystem/ImageEditorActionUtil.java
new file mode 100644
index 000000000000..1f6e6f8e2a40
--- /dev/null
+++ b/images/src/org/intellij/images/editor/actionSystem/ImageEditorActionUtil.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.actionSystem;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.fileEditor.FileEditor;
+import org.intellij.images.editor.ImageEditor;
+import org.intellij.images.editor.ImageFileEditor;
+
+/**
+ * Editor actions utility.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public final class ImageEditorActionUtil {
+ private ImageEditorActionUtil() {
+ }
+
+ /**
+ * Extract current editor from event context.
+ *
+ * @param e Action event
+ * @return Current {@link ImageEditor} or <code>null</code>
+ */
+ public static ImageEditor getValidEditor(AnActionEvent e) {
+ ImageEditor editor = getEditor(e);
+ if (editor != null && editor.isValid()) {
+ return editor;
+ }
+ return null;
+ }
+
+ public static ImageEditor getEditor(AnActionEvent e) {
+ DataContext dataContext = e.getDataContext();
+ FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(dataContext);
+ if (editor instanceof ImageFileEditor) {
+ ImageFileEditor fileEditor = (ImageFileEditor) editor;
+ return fileEditor.getImageEditor();
+ }
+ return null;
+ }
+
+ /**
+ * Enable or disable current action from event.
+ *
+ * @param e Action event
+ * @return Enabled value
+ */
+ public static boolean setEnabled(AnActionEvent e) {
+ ImageEditor editor = getValidEditor(e);
+ Presentation presentation = e.getPresentation();
+ presentation.setEnabled(editor != null);
+ return presentation.isEnabled();
+ }
+}
diff --git a/images/src/org/intellij/images/editor/actionSystem/ImageEditorActions.java b/images/src/org/intellij/images/editor/actionSystem/ImageEditorActions.java
new file mode 100644
index 000000000000..a9b4668dca91
--- /dev/null
+++ b/images/src/org/intellij/images/editor/actionSystem/ImageEditorActions.java
@@ -0,0 +1,32 @@
+/*
+ * 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.actionSystem;
+
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * Editor actions.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface ImageEditorActions {
+ @NonNls
+ String GROUP_TOOLBAR = "Images.EditorToolbar";
+ @NonNls
+ String GROUP_POPUP = "Images.EditorPopupMenu";
+ @NonNls
+ String ACTION_PLACE = "Images.Editor";
+}
diff --git a/images/src/org/intellij/images/editor/actions/ActualSizeAction.java b/images/src/org/intellij/images/editor/actions/ActualSizeAction.java
new file mode 100644
index 000000000000..556e26f846b1
--- /dev/null
+++ b/images/src/org/intellij/images/editor/actions/ActualSizeAction.java
@@ -0,0 +1,48 @@
+/*
+ * 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.actions;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import org.intellij.images.editor.ImageEditor;
+import org.intellij.images.editor.ImageZoomModel;
+import org.intellij.images.editor.actionSystem.ImageEditorActionUtil;
+
+/**
+ * Resize image to actual size.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ * @see ImageEditor#getZoomModel()
+ * @see ImageZoomModel#setZoomFactor
+ */
+public final class ActualSizeAction extends AnAction {
+ public void actionPerformed(AnActionEvent e) {
+ ImageEditor editor = ImageEditorActionUtil.getValidEditor(e);
+ if (editor != null) {
+ ImageZoomModel zoomModel = editor.getZoomModel();
+ zoomModel.setZoomFactor(1.0d);
+ }
+ }
+
+ public void update(AnActionEvent e) {
+ super.update(e);
+ if (ImageEditorActionUtil.setEnabled(e)) {
+ ImageEditor editor = ImageEditorActionUtil.getValidEditor(e);
+ ImageZoomModel zoomModel = editor.getZoomModel();
+ e.getPresentation().setEnabled(zoomModel.getZoomFactor() != 1.0d);
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/editor/actions/ToggleGridAction.java b/images/src/org/intellij/images/editor/actions/ToggleGridAction.java
new file mode 100644
index 000000000000..53ca4b2f7354
--- /dev/null
+++ b/images/src/org/intellij/images/editor/actions/ToggleGridAction.java
@@ -0,0 +1,47 @@
+/*
+ * 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.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.ToggleAction;
+import org.intellij.images.editor.ImageEditor;
+import org.intellij.images.editor.actionSystem.ImageEditorActionUtil;
+
+/**
+ * Toggle grid lines over image.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ * @see ImageEditor#setGridVisible
+ */
+public final class ToggleGridAction extends ToggleAction {
+ public boolean isSelected(AnActionEvent e) {
+ ImageEditor editor = ImageEditorActionUtil.getValidEditor(e);
+ return editor != null && editor.isGridVisible();
+ }
+
+ public void setSelected(AnActionEvent e, boolean state) {
+ ImageEditor editor = ImageEditorActionUtil.getValidEditor(e);
+ if (editor != null) {
+ editor.setGridVisible(state);
+ }
+ }
+
+ public void update(final AnActionEvent e) {
+ super.update(e);
+ ImageEditorActionUtil.setEnabled(e);
+ e.getPresentation().setText(isSelected(e) ? "Hide Grid" : "Show Grid");
+ }
+}
diff --git a/images/src/org/intellij/images/editor/actions/ZoomInAction.java b/images/src/org/intellij/images/editor/actions/ZoomInAction.java
new file mode 100644
index 000000000000..54d11ef81b9c
--- /dev/null
+++ b/images/src/org/intellij/images/editor/actions/ZoomInAction.java
@@ -0,0 +1,47 @@
+/*
+ * 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.actions;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import org.intellij.images.editor.ImageEditor;
+import org.intellij.images.editor.ImageZoomModel;
+import org.intellij.images.editor.actionSystem.ImageEditorActionUtil;
+
+/**
+ * Zoom in.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ * @see ImageEditor#getZoomModel
+ */
+public final class ZoomInAction extends AnAction {
+ public void actionPerformed(AnActionEvent e) {
+ ImageEditor editor = ImageEditorActionUtil.getValidEditor(e);
+ if (editor != null) {
+ ImageZoomModel zoomModel = editor.getZoomModel();
+ zoomModel.zoomIn();
+ }
+ }
+
+ public void update(AnActionEvent e) {
+ super.update(e);
+ if (ImageEditorActionUtil.setEnabled(e)) {
+ ImageEditor editor = ImageEditorActionUtil.getValidEditor(e);
+ ImageZoomModel zoomModel = editor.getZoomModel();
+ e.getPresentation().setEnabled(zoomModel.canZoomIn());
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/editor/actions/ZoomOutAction.java b/images/src/org/intellij/images/editor/actions/ZoomOutAction.java
new file mode 100644
index 000000000000..babd5f47a190
--- /dev/null
+++ b/images/src/org/intellij/images/editor/actions/ZoomOutAction.java
@@ -0,0 +1,47 @@
+/*
+ * 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.actions;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import org.intellij.images.editor.ImageEditor;
+import org.intellij.images.editor.ImageZoomModel;
+import org.intellij.images.editor.actionSystem.ImageEditorActionUtil;
+
+/**
+ * Zoom out.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ * @see ImageEditor#getZoomModel
+ */
+public final class ZoomOutAction extends AnAction {
+ public void actionPerformed(AnActionEvent e) {
+ ImageEditor editor = ImageEditorActionUtil.getValidEditor(e);
+ if (editor != null) {
+ ImageZoomModel zoomModel = editor.getZoomModel();
+ zoomModel.zoomOut();
+ }
+ }
+
+ public void update(AnActionEvent e) {
+ super.update(e);
+ if (ImageEditorActionUtil.setEnabled(e)) {
+ ImageEditor editor = ImageEditorActionUtil.getValidEditor(e);
+ ImageZoomModel zoomModel = editor.getZoomModel();
+ e.getPresentation().setEnabled(zoomModel.canZoomOut());
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/editor/impl/ImageEditorImpl.java b/images/src/org/intellij/images/editor/impl/ImageEditorImpl.java
new file mode 100644
index 000000000000..11319e33440a
--- /dev/null
+++ b/images/src/org/intellij/images/editor/impl/ImageEditorImpl.java
@@ -0,0 +1,208 @@
+/*
+ * 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.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.*;
+import org.intellij.images.editor.ImageDocument;
+import org.intellij.images.editor.ImageEditor;
+import org.intellij.images.editor.ImageZoomModel;
+import org.intellij.images.fileTypes.ImageFileTypeManager;
+import org.intellij.images.options.*;
+import org.intellij.images.thumbnail.actionSystem.ThumbnailViewActions;
+import org.intellij.images.ui.ImageComponent;
+import org.intellij.images.vfs.IfsUtil;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+/**
+ * Image viewer implementation.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class ImageEditorImpl extends VirtualFileAdapter implements ImageEditor {
+ private final PropertyChangeListener optionsChangeListener = new OptionsChangeListener();
+ private final Project project;
+ private final VirtualFile file;
+ private final ImageEditorUI editorUI;
+ private boolean disposed;
+
+ ImageEditorImpl(@NotNull Project project, @NotNull VirtualFile file) {
+ this.project = project;
+ this.file = file;
+
+ // Options
+ Options options = OptionsManager.getInstance().getOptions();
+ editorUI = new ImageEditorUI(this, options.getEditorOptions());
+ options.addPropertyChangeListener(optionsChangeListener);
+
+ VirtualFileManager.getInstance().addVirtualFileListener(this);
+
+ setValue(file);
+ }
+
+ private void setValue(VirtualFile file) {
+ ImageDocument document = editorUI.getImageComponent().getDocument();
+ try {
+ BufferedImage previousImage = document.getValue();
+ BufferedImage image = IfsUtil.getImage(file);
+ document.setValue(image);
+ document.setFormat(IfsUtil.getFormat(file));
+ ImageZoomModel zoomModel = getZoomModel();
+ if (image != null && (previousImage == null || !zoomModel.isZoomLevelChanged())) {
+ // Set smart zooming behaviour on open
+ Options options = OptionsManager.getInstance().getOptions();
+ ZoomOptions zoomOptions = options.getEditorOptions().getZoomOptions();
+ // Open as actual size
+ zoomModel.setZoomFactor(1.0d);
+
+ if (zoomOptions.isSmartZooming()) {
+ Dimension prefferedSize = zoomOptions.getPrefferedSize();
+ if (prefferedSize.width > image.getWidth() && prefferedSize.height > image.getHeight()) {
+ // Resize to preffered size
+ // Calculate zoom factor
+
+ double factor = (prefferedSize.getWidth() / (double) image.getWidth() + prefferedSize.getHeight() / (double) image.getHeight()) / 2.0d;
+ zoomModel.setZoomFactor(Math.ceil(factor));
+ }
+ }
+ }
+ } catch (Exception e) {
+ // Error loading image file
+ document.setValue(null);
+ }
+ }
+
+ public boolean isValid() {
+ ImageDocument document = editorUI.getImageComponent().getDocument();
+ return document.getValue() != null;
+ }
+
+ public JComponent getComponent() {
+ return editorUI;
+ }
+
+ public JComponent getContentComponent() {
+ return editorUI.getImageComponent();
+ }
+
+ @NotNull
+ public VirtualFile getFile() {
+ return file;
+ }
+
+ @NotNull
+ public Project getProject() {
+ return project;
+ }
+
+ public ImageDocument getDocument() {
+ return editorUI.getImageComponent().getDocument();
+ }
+
+ public void setTransparencyChessboardVisible(boolean visible) {
+ editorUI.getImageComponent().setTransparencyChessboardVisible(visible);
+ editorUI.repaint();
+ }
+
+ public boolean isTransparencyChessboardVisible() {
+ return editorUI.getImageComponent().isTransparencyChessboardVisible();
+ }
+
+ public boolean isEnabledForActionPlace(String place) {
+ // Disable for thumbnails action
+ return !ThumbnailViewActions.ACTION_PLACE.equals(place);
+ }
+
+ public void setGridVisible(boolean visible) {
+ editorUI.getImageComponent().setGridVisible(visible);
+ editorUI.repaint();
+ }
+
+ public boolean isGridVisible() {
+ return editorUI.getImageComponent().isGridVisible();
+ }
+
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ public ImageZoomModel getZoomModel() {
+ return editorUI.getZoomModel();
+ }
+
+ public void dispose() {
+ Options options = OptionsManager.getInstance().getOptions();
+ options.removePropertyChangeListener(optionsChangeListener);
+ editorUI.dispose();
+ VirtualFileManager.getInstance().removeVirtualFileListener(this);
+ disposed = true;
+ }
+
+ public void propertyChanged(VirtualFilePropertyEvent event) {
+ super.propertyChanged(event);
+ if (file.equals(event.getFile())) {
+ // Change document
+ file.refresh(true, false, new Runnable() {
+ public void run() {
+ if (ImageFileTypeManager.getInstance().isImage(file)) {
+ setValue(file);
+ } else {
+ setValue(null);
+ // Close editor
+ FileEditorManager editorManager = FileEditorManager.getInstance(project);
+ editorManager.closeFile(file);
+ }
+ }
+ });
+ }
+ }
+
+ public void contentsChanged(VirtualFileEvent event) {
+ super.contentsChanged(event);
+ if (file.equals(event.getFile())) {
+ // Change document
+ file.refresh(true, false, new Runnable() {
+ public void run() {
+ setValue(file);
+ }
+ });
+ }
+ }
+
+ 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 imageComponent = editorUI.getImageComponent();
+ imageComponent.setTransparencyChessboardCellSize(chessboardOptions.getCellSize());
+ imageComponent.setTransparencyChessboardWhiteColor(chessboardOptions.getWhiteColor());
+ imageComponent.setTransparencyChessboardBlankColor(chessboardOptions.getBlackColor());
+ imageComponent.setGridLineZoomFactor(gridOptions.getLineZoomFactor());
+ imageComponent.setGridLineSpan(gridOptions.getLineSpan());
+ imageComponent.setGridLineColor(gridOptions.getLineColor());
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/editor/impl/ImageEditorManagerImpl.java b/images/src/org/intellij/images/editor/impl/ImageEditorManagerImpl.java
new file mode 100644
index 000000000000..815c09308e22
--- /dev/null
+++ b/images/src/org/intellij/images/editor/impl/ImageEditorManagerImpl.java
@@ -0,0 +1,54 @@
+/*
+ * 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.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.intellij.images.editor.ImageEditor;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Image viewer manager implementation.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class ImageEditorManagerImpl {
+ private ImageEditorManagerImpl() {
+ }
+
+ /**
+ * Create image viewer editor. Don't forget release editor by {@link #releaseImageEditor(ImageEditor)} method.
+ *
+ * @param project Project
+ * @param file File
+ * @return Image editor for file
+ */
+ @NotNull
+ public static ImageEditor createImageEditor(@NotNull Project project, @NotNull VirtualFile file) {
+ return new ImageEditorImpl(project, file);
+ }
+
+ /**
+ * Release editor. Disposing caches and other resources allocated in creation.
+ *
+ * @param editor Editor to release.
+ */
+ public static void releaseImageEditor(@NotNull ImageEditor editor) {
+ if (!editor.isDisposed()) {
+ editor.dispose();
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/editor/impl/ImageEditorUI.java b/images/src/org/intellij/images/editor/impl/ImageEditorUI.java
new file mode 100644
index 000000000000..0cd9f275cdc2
--- /dev/null
+++ b/images/src/org/intellij/images/editor/impl/ImageEditorUI.java
@@ -0,0 +1,379 @@
+/*
+ * 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.DeleteProvider;
+import com.intellij.ide.PsiActionSupportFactory;
+import com.intellij.openapi.actionSystem.*;
+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.PopupHandler;
+import com.intellij.ui.ScrollPaneFactory;
+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.Nullable;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+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;
+
+/**
+ * Image editor UI
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class ImageEditorUI extends JPanel implements DataProvider {
+ @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();
+ copyPasteSupport = factory.createPsiBasedCopyPasteSupport(editor.getProject(), this, new PsiActionSupportFactory.PsiElementSelector() {
+ public PsiElement[] getSelectedElements() {
+ return LangDataKeys.PSI_ELEMENT_ARRAY.getData(ImageEditorUI.this);
+ }
+ });
+
+ deleteProvider = 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(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+ scrollPane.setHorizontalScrollBarPolicy(JScrollPane.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(), JLabel.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, JLabel.RIGHT);
+ 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();
+ }
+ 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);
+ }
+ }
+
+ JComponent getContentComponent() {
+ return contentPanel;
+ }
+
+ ImageComponent getImageComponent() {
+ return imageComponent;
+ }
+
+ void dispose() {
+ imageComponent.removeMouseWheelListener(wheelAdapter);
+ imageComponent.getDocument().removeChangeListener(changeListener);
+
+ removeAll();
+ }
+
+ ImageZoomModel getZoomModel() {
+ return zoomModel;
+ }
+
+ private static final class ImageContainerPane extends JLayeredPane {
+ private final ImageComponent imageComponent;
+
+ public ImageContainerPane(ImageComponent imageComponent) {
+ this.imageComponent = imageComponent;
+ add(imageComponent);
+ }
+
+ 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(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(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(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 (PlatformDataKeys.PROJECT.is(dataId)) {
+ return editor.getProject();
+ } else if (PlatformDataKeys.VIRTUAL_FILE.is(dataId)) {
+ return editor.getFile();
+ } else if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
+ return new VirtualFile[]{editor.getFile()};
+ } else if (LangDataKeys.PSI_FILE.is(dataId)) {
+ return getData(LangDataKeys.PSI_ELEMENT.getName());
+ } else if (LangDataKeys.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(LangDataKeys.PSI_ELEMENT.getName())};
+ } else if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
+ return copyPasteSupport.getCopyProvider();
+ } else if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
+ return copyPasteSupport.getCutProvider();
+ } else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
+ return deleteProvider;
+ } else if (ImageComponentDecorator.DATA_KEY.is(dataId)) {
+ return editor;
+ }
+
+ return null;
+ }
+}
diff --git a/images/src/org/intellij/images/editor/impl/ImageFileEditorImpl.java b/images/src/org/intellij/images/editor/impl/ImageFileEditorImpl.java
new file mode 100644
index 000000000000..1c2b6c45ffbd
--- /dev/null
+++ b/images/src/org/intellij/images/editor/impl/ImageFileEditorImpl.java
@@ -0,0 +1,136 @@
+/*
+ * 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.codeHighlighting.BackgroundEditorHighlighter;
+import com.intellij.ide.structureView.StructureViewBuilder;
+import com.intellij.openapi.fileEditor.FileEditorLocation;
+import com.intellij.openapi.fileEditor.FileEditorState;
+import com.intellij.openapi.fileEditor.FileEditorStateLevel;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.UserDataHolderBase;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import org.intellij.images.editor.ImageEditor;
+import org.intellij.images.editor.ImageFileEditor;
+import org.intellij.images.editor.ImageZoomModel;
+import org.intellij.images.options.*;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.beans.PropertyChangeListener;
+
+/**
+ * Image Editor.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class ImageFileEditorImpl extends UserDataHolderBase implements ImageFileEditor {
+ @NonNls
+ private static final String NAME = "ImageFileEditor";
+ private final ImageEditor imageEditor;
+
+ ImageFileEditorImpl(@NotNull Project project, @NotNull VirtualFile file) {
+ imageEditor = ImageEditorManagerImpl.createImageEditor(project, file);
+
+ // Append file listener
+ VirtualFileManager.getInstance().addVirtualFileListener(imageEditor);
+
+ // Set background and grid default options
+ Options options = OptionsManager.getInstance().getOptions();
+ EditorOptions editorOptions = options.getEditorOptions();
+ GridOptions gridOptions = editorOptions.getGridOptions();
+ TransparencyChessboardOptions transparencyChessboardOptions = editorOptions.getTransparencyChessboardOptions();
+ imageEditor.setGridVisible(gridOptions.isShowDefault());
+ imageEditor.setTransparencyChessboardVisible(transparencyChessboardOptions.isShowDefault());
+ }
+
+ @NotNull
+ public JComponent getComponent() {
+ return imageEditor.getComponent();
+ }
+
+ public JComponent getPreferredFocusedComponent() {
+ return imageEditor.getContentComponent();
+ }
+
+ @NotNull
+ public String getName() {
+ return NAME;
+ }
+
+ @NotNull
+ public FileEditorState getState(@NotNull FileEditorStateLevel level) {
+ ImageZoomModel zoomModel = imageEditor.getZoomModel();
+ return new ImageFileEditorState(
+ imageEditor.isTransparencyChessboardVisible(),
+ imageEditor.isGridVisible(),
+ zoomModel.getZoomFactor());
+ }
+
+ public void setState(@NotNull FileEditorState state) {
+ if (state instanceof ImageFileEditorState) {
+ ImageFileEditorState editorState = (ImageFileEditorState) state;
+ ImageZoomModel zoomModel = imageEditor.getZoomModel();
+ imageEditor.setTransparencyChessboardVisible(editorState.isBackgroundVisible());
+ imageEditor.setGridVisible(editorState.isGridVisible());
+ zoomModel.setZoomFactor(editorState.getZoomFactor());
+ }
+ }
+
+ public boolean isModified() {
+ return false;
+ }
+
+ public boolean isValid() {
+ return true;
+ }
+
+ public void selectNotify() {
+ }
+
+ public void deselectNotify() {
+ }
+
+ public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
+ }
+
+ public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
+ }
+
+ public BackgroundEditorHighlighter getBackgroundHighlighter() {
+ return null;
+ }
+
+ public FileEditorLocation getCurrentLocation() {
+ return null;
+ }
+
+ public StructureViewBuilder getStructureViewBuilder() {
+ return null;
+ }
+
+ public void dispose() {
+ VirtualFileManager.getInstance().removeVirtualFileListener(imageEditor);
+ ImageEditorManagerImpl.releaseImageEditor(imageEditor);
+ }
+
+ @NotNull
+ public ImageEditor getImageEditor() {
+ return imageEditor;
+ }
+}
diff --git a/images/src/org/intellij/images/editor/impl/ImageFileEditorProvider.java b/images/src/org/intellij/images/editor/impl/ImageFileEditorProvider.java
new file mode 100644
index 000000000000..da0f8f1c9a56
--- /dev/null
+++ b/images/src/org/intellij/images/editor/impl/ImageFileEditorProvider.java
@@ -0,0 +1,76 @@
+/*
+ * 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.openapi.fileEditor.*;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.intellij.images.fileTypes.ImageFileTypeManager;
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Image editor provider.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class ImageFileEditorProvider implements FileEditorProvider, DumbAware {
+ @NonNls private static final String EDITOR_TYPE_ID = "images";
+
+ private final ImageFileTypeManager typeManager;
+
+ ImageFileEditorProvider(ImageFileTypeManager typeManager) {
+ this.typeManager = typeManager;
+ }
+
+ public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
+ return typeManager.isImage(file);
+ }
+
+ @NotNull
+ public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) {
+ return new ImageFileEditorImpl(project, file);
+ }
+
+ public void disposeEditor(@NotNull FileEditor editor) {
+ Disposer.dispose(editor);
+ }
+
+ @NotNull
+ public FileEditorState readState(@NotNull Element sourceElement, @NotNull Project project, @NotNull VirtualFile file) {
+ return new FileEditorState() {
+ public boolean canBeMergedWith(FileEditorState otherState, FileEditorStateLevel level) {
+ return false;
+ }
+ };
+ }
+
+ public void writeState(@NotNull FileEditorState state, @NotNull Project project, @NotNull Element targetElement) {
+ }
+
+ @NotNull
+ public String getEditorTypeId() {
+ return EDITOR_TYPE_ID;
+ }
+
+ @NotNull
+ public FileEditorPolicy getPolicy() {
+ return FileEditorPolicy.HIDE_DEFAULT_EDITOR;
+ }
+}
diff --git a/images/src/org/intellij/images/editor/impl/ImageFileEditorState.java b/images/src/org/intellij/images/editor/impl/ImageFileEditorState.java
new file mode 100644
index 000000000000..6a7dab4d116a
--- /dev/null
+++ b/images/src/org/intellij/images/editor/impl/ImageFileEditorState.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * 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.openapi.fileEditor.FileEditorState;
+import com.intellij.openapi.fileEditor.FileEditorStateLevel;
+import com.intellij.openapi.fileEditor.TransferableFileEditorState;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Konstantin Bulenkov
+ */
+public class ImageFileEditorState implements TransferableFileEditorState, Serializable {
+ private static final long serialVersionUID = -4470317464706072486L;
+ public static final String IMAGE_EDITOR_ID = "ImageEditor";
+ public static final String BACKGROUND_VISIBLE_OPTION = "backgroundVisible";
+ public static final String GRID_VISIBLE_OPTION = "gridVisible";
+ public static final String ZOOM_FACTOR_OPTION = "zoomFactor";
+
+ private boolean backgroundVisible;
+ private boolean gridVisible;
+ private double zoomFactor;
+
+ ImageFileEditorState(boolean backgroundVisible, boolean gridVisible, double zoomFactor) {
+ this.backgroundVisible = backgroundVisible;
+ this.gridVisible = gridVisible;
+ this.zoomFactor = zoomFactor;
+ }
+
+ public boolean canBeMergedWith(FileEditorState otherState, FileEditorStateLevel level) {
+ return otherState instanceof ImageFileEditorState;
+ }
+
+ public boolean isBackgroundVisible() {
+ return backgroundVisible;
+ }
+
+ public boolean isGridVisible() {
+ return gridVisible;
+ }
+
+ public double getZoomFactor() {
+ return zoomFactor;
+ }
+
+ @Override
+ public String getEditorId() {
+ return IMAGE_EDITOR_ID;
+ }
+
+ @Override
+ public Map<String, String> getTransferableOptions() {
+ final HashMap<String, String> map = new HashMap<String, String>();
+ map.put(BACKGROUND_VISIBLE_OPTION, String.valueOf(backgroundVisible));
+ map.put(GRID_VISIBLE_OPTION, String.valueOf(gridVisible));
+ map.put(ZOOM_FACTOR_OPTION, String.valueOf(zoomFactor));
+ return map;
+ }
+
+ @Override
+ public void setTransferableOptions(Map<String, String> options) {
+ String o = options.get(BACKGROUND_VISIBLE_OPTION);
+ if (o != null) {
+ backgroundVisible = Boolean.valueOf(o);
+ }
+
+ o = options.get(GRID_VISIBLE_OPTION);
+ if (o != null) {
+ gridVisible = Boolean.valueOf(o);
+ }
+
+ o = options.get(ZOOM_FACTOR_OPTION);
+ if (o != null) {
+ zoomFactor = Double.parseDouble(o);
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/fileTypes/ImageDocumentationProvider.java b/images/src/org/intellij/images/fileTypes/ImageDocumentationProvider.java
new file mode 100644
index 000000000000..e0698ed140b1
--- /dev/null
+++ b/images/src/org/intellij/images/fileTypes/ImageDocumentationProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.fileTypes;
+
+import com.intellij.lang.documentation.AbstractDocumentationProvider;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileWithId;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.util.indexing.FileBasedIndex;
+import org.intellij.images.index.ImageInfoIndex;
+import org.jetbrains.annotations.Nullable;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * @author spleaner
+ */
+public class ImageDocumentationProvider extends AbstractDocumentationProvider {
+ private static final int MAX_IMAGE_SIZE = 300;
+
+ @Override
+ public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
+ final String[] result = new String[] {null};
+
+ if (element instanceof PsiFileSystemItem && !((PsiFileSystemItem)element).isDirectory()) {
+ final VirtualFile file = ((PsiFileSystemItem)element).getVirtualFile();
+ if (file instanceof VirtualFileWithId && !DumbService.isDumb(element.getProject())) {
+ ImageInfoIndex.processValues(file, new FileBasedIndex.ValueProcessor<ImageInfoIndex.ImageInfo>() {
+ public boolean process(VirtualFile file, ImageInfoIndex.ImageInfo value) {
+ int imageWidth = value.width;
+ int imageHeight = value.height;
+
+ int maxSize = Math.max(value.width, value.height);
+ if (maxSize > MAX_IMAGE_SIZE) {
+ double scaleFactor = (double)MAX_IMAGE_SIZE / (double)maxSize;
+ imageWidth *= scaleFactor;
+ imageHeight *= scaleFactor;
+ }
+ try {
+ String path = file.getPath();
+ if (SystemInfo.isWindows) {
+ path = "/" + path;
+ }
+ final String url = new URI("file", null, path, null).toString();
+ result[0] = String.format("<html><body><img src=\"%s\" width=\"%s\" height=\"%s\"><p>%sx%s, %sbpp</p><body></html>", url, imageWidth,
+ imageHeight, value.width, value.height, value.bpp);
+ }
+ catch (URISyntaxException e) {
+ // nothing
+ }
+ return true;
+ }
+ }, element.getProject());
+ }
+ }
+
+ return result[0];
+ }
+}
diff --git a/images/src/org/intellij/images/fileTypes/ImageFileTypeManager.java b/images/src/org/intellij/images/fileTypes/ImageFileTypeManager.java
new file mode 100644
index 000000000000..693124292398
--- /dev/null
+++ b/images/src/org/intellij/images/fileTypes/ImageFileTypeManager.java
@@ -0,0 +1,44 @@
+/*
+ * 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.fileTypes;
+
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypeFactory;
+import com.intellij.openapi.vfs.VirtualFile;
+
+/**
+ * File type manager.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public abstract class ImageFileTypeManager extends FileTypeFactory {
+ public static ImageFileTypeManager getInstance() {
+ Application application = ApplicationManager.getApplication();
+ return application.getComponent(ImageFileTypeManager.class);
+ }
+
+ /**
+ * Check that file is image.
+ *
+ * @param file File to check
+ * @return Return <code>true</code> if image file is file with Images file type
+ */
+ public abstract boolean isImage(VirtualFile file);
+
+ public abstract FileType getImageFileType();
+}
diff --git a/images/src/org/intellij/images/fileTypes/impl/ImageFileTypeManagerImpl.java b/images/src/org/intellij/images/fileTypes/impl/ImageFileTypeManagerImpl.java
new file mode 100644
index 000000000000..adc75a4f19ac
--- /dev/null
+++ b/images/src/org/intellij/images/fileTypes/impl/ImageFileTypeManagerImpl.java
@@ -0,0 +1,92 @@
+/*
+ * 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.fileTypes.impl;
+
+import com.intellij.openapi.components.ApplicationComponent;
+import com.intellij.openapi.fileTypes.*;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import gnu.trove.THashSet;
+import icons.ImagesIcons;
+import org.intellij.images.ImagesBundle;
+import org.intellij.images.fileTypes.ImageFileTypeManager;
+import org.intellij.images.vfs.IfsUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import javax.imageio.ImageIO;
+import java.util.Set;
+
+/**
+ * Image file type manager.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class ImageFileTypeManagerImpl extends ImageFileTypeManager implements ApplicationComponent {
+ @NonNls private static final String NAME = "ImagesFileTypeManager";
+
+ @NonNls private static final String IMAGE_FILE_TYPE_NAME = "Images";
+ private static final String IMAGE_FILE_TYPE_DESCRIPTION = ImagesBundle.message("images.filetype.description");
+ private static final UserFileType imageFileType;
+
+ static {
+ imageFileType = new ImageFileType();
+ imageFileType.setIcon(ImagesIcons.ImagesFileType);
+ imageFileType.setName(IMAGE_FILE_TYPE_NAME);
+ imageFileType.setDescription(IMAGE_FILE_TYPE_DESCRIPTION);
+ }
+
+ public ImageFileTypeManagerImpl() {
+ }
+
+ public boolean isImage(VirtualFile file) {
+ FileTypeManager fileTypeManager = FileTypeManager.getInstance();
+ FileType fileTypeByFile = file.getFileType();
+ return fileTypeByFile instanceof ImageFileType;
+ }
+
+ public FileType getImageFileType() {
+ return imageFileType;
+ }
+
+ @NotNull
+ public String getComponentName() {
+ return NAME;
+ }
+
+ public void initComponent() {
+ }
+
+ public void disposeComponent() {
+ }
+
+ public static final class ImageFileType extends UserBinaryFileType {
+ }
+
+ public void createFileTypes(final @NotNull FileTypeConsumer consumer) {
+ final Set<String> processed = new THashSet<String>();
+
+ final String[] readerFormatNames = ImageIO.getReaderFormatNames();
+ for (String format : readerFormatNames) {
+ final String ext = format.toLowerCase();
+ processed.add(ext);
+ }
+
+ processed.add(IfsUtil.ICO_FORMAT.toLowerCase());
+
+ consumer.consume(imageFileType, StringUtil.join(processed, FileTypeConsumer.EXTENSION_DELIMITER));
+ }
+}
diff --git a/images/src/org/intellij/images/icons/EditExternaly.png b/images/src/org/intellij/images/icons/EditExternaly.png
new file mode 100644
index 000000000000..23e5ce91e5b6
--- /dev/null
+++ b/images/src/org/intellij/images/icons/EditExternaly.png
Binary files differ
diff --git a/images/src/org/intellij/images/icons/ImagesFileType.png b/images/src/org/intellij/images/icons/ImagesFileType.png
new file mode 100644
index 000000000000..918fa6233f42
--- /dev/null
+++ b/images/src/org/intellij/images/icons/ImagesFileType.png
Binary files differ
diff --git a/images/src/org/intellij/images/icons/ThumbnailBlank.png b/images/src/org/intellij/images/icons/ThumbnailBlank.png
new file mode 100644
index 000000000000..53deaf8f77a4
--- /dev/null
+++ b/images/src/org/intellij/images/icons/ThumbnailBlank.png
Binary files differ
diff --git a/images/src/org/intellij/images/icons/ThumbnailDirectory.png b/images/src/org/intellij/images/icons/ThumbnailDirectory.png
new file mode 100644
index 000000000000..7d4afcc7423a
--- /dev/null
+++ b/images/src/org/intellij/images/icons/ThumbnailDirectory.png
Binary files differ
diff --git a/images/src/org/intellij/images/icons/ThumbnailToolWindow.png b/images/src/org/intellij/images/icons/ThumbnailToolWindow.png
new file mode 100644
index 000000000000..377a001dd602
--- /dev/null
+++ b/images/src/org/intellij/images/icons/ThumbnailToolWindow.png
Binary files differ
diff --git a/images/src/org/intellij/images/icons/ToggleGrid.png b/images/src/org/intellij/images/icons/ToggleGrid.png
new file mode 100644
index 000000000000..badec3ac01b8
--- /dev/null
+++ b/images/src/org/intellij/images/icons/ToggleGrid.png
Binary files differ
diff --git a/images/src/org/intellij/images/icons/ToggleTransparencyChessboard.png b/images/src/org/intellij/images/icons/ToggleTransparencyChessboard.png
new file mode 100644
index 000000000000..032f07149032
--- /dev/null
+++ b/images/src/org/intellij/images/icons/ToggleTransparencyChessboard.png
Binary files differ
diff --git a/images/src/org/intellij/images/index/ImageInfoIndex.java b/images/src/org/intellij/images/index/ImageInfoIndex.java
new file mode 100644
index 000000000000..10a008eae8dd
--- /dev/null
+++ b/images/src/org/intellij/images/index/ImageInfoIndex.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.index;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.ex.temp.TempFileSystem;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.util.indexing.*;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.DataInputOutputUtil;
+import org.intellij.images.fileTypes.ImageFileTypeManager;
+import org.intellij.images.util.ImageInfoReader;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * @author spleaner
+ */
+public class ImageInfoIndex extends SingleEntryFileBasedIndexExtension<ImageInfoIndex.ImageInfo> {
+ private static final int ourMaxImageSize;
+ static {
+ int maxImageSize = 200;
+ try {
+ maxImageSize = Integer.parseInt(System.getProperty("idea.max.image.filesize", Integer.toString(maxImageSize)), 10);
+ } catch (NumberFormatException ex) {}
+ ourMaxImageSize = maxImageSize;
+ }
+
+ public static final ID<Integer, ImageInfo> INDEX_ID = ID.create("ImageFileInfoIndex");
+
+ private final FileBasedIndex.InputFilter myInputFilter = new FileBasedIndex.InputFilter() {
+ @Override
+ public boolean acceptInput(final VirtualFile file) {
+ return (file.getFileSystem() == LocalFileSystem.getInstance() || file.getFileSystem() instanceof TempFileSystem) &&
+ file.getFileType() == ImageFileTypeManager.getInstance().getImageFileType() &&
+ (file.getLength() / 1024) < ourMaxImageSize
+ ;
+ }
+ };
+
+ private final DataExternalizer<ImageInfo> myValueExternalizer = new DataExternalizer<ImageInfo>() {
+ @Override
+ public void save(final DataOutput out, final ImageInfo info) throws IOException {
+ DataInputOutputUtil.writeINT(out, info.width);
+ DataInputOutputUtil.writeINT(out, info.height);
+ DataInputOutputUtil.writeINT(out, info.bpp);
+ }
+
+ @Override
+ public ImageInfo read(final DataInput in) throws IOException {
+ return new ImageInfo(DataInputOutputUtil.readINT(in), DataInputOutputUtil.readINT(in), DataInputOutputUtil.readINT(in));
+ }
+ };
+
+ private final SingleEntryIndexer<ImageInfo> myDataIndexer = new SingleEntryIndexer<ImageInfo>(false) {
+ @Override
+ protected ImageInfo computeValue(@NotNull FileContent inputData) {
+ final ImageInfoReader.Info info = ImageInfoReader.getInfo(inputData.getContent());
+ return info != null? new ImageInfo(info.width, info.height, info.bpp) : null;
+ }
+ };
+
+ @Override
+ @NotNull
+ public ID<Integer, ImageInfo> getName() {
+ return INDEX_ID;
+ }
+
+ @Override
+ @NotNull
+ public SingleEntryIndexer<ImageInfo> getIndexer() {
+ return myDataIndexer;
+ }
+
+ public static void processValues(VirtualFile virtualFile, FileBasedIndex.ValueProcessor<ImageInfo> processor, Project project) {
+ FileBasedIndex.getInstance().processValues(INDEX_ID, Math.abs(FileBasedIndex.getFileId(virtualFile)), virtualFile, processor, GlobalSearchScope
+ .fileScope(project, virtualFile));
+ }
+
+ @Override
+ public DataExternalizer<ImageInfo> getValueExternalizer() {
+ return myValueExternalizer;
+ }
+
+ @Override
+ public FileBasedIndex.InputFilter getInputFilter() {
+ return myInputFilter;
+ }
+
+ @Override
+ public int getVersion() {
+ return 5;
+ }
+
+ public static class ImageInfo {
+ public int width;
+ public int height;
+ public int bpp;
+
+ public ImageInfo(int width, int height, int bpp) {
+ this.width = width;
+ this.height = height;
+ this.bpp = bpp;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ImageInfo imageInfo = (ImageInfo)o;
+
+ if (bpp != imageInfo.bpp) return false;
+ if (height != imageInfo.height) return false;
+ if (width != imageInfo.width) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = width;
+ result = 31 * result + height;
+ result = 31 * result + bpp;
+ return result;
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/options/EditorOptions.java b/images/src/org/intellij/images/options/EditorOptions.java
new file mode 100644
index 000000000000..a2a7cb67a858
--- /dev/null
+++ b/images/src/org/intellij/images/options/EditorOptions.java
@@ -0,0 +1,33 @@
+/*
+ * 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.options;
+
+/**
+ * Images editor options.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface EditorOptions extends Cloneable {
+ GridOptions getGridOptions();
+
+ TransparencyChessboardOptions getTransparencyChessboardOptions();
+
+ ZoomOptions getZoomOptions();
+
+ void inject(EditorOptions options);
+
+ boolean setOption(String name, Object value);
+}
diff --git a/images/src/org/intellij/images/options/ExternalEditorOptions.java b/images/src/org/intellij/images/options/ExternalEditorOptions.java
new file mode 100644
index 000000000000..775c81a3e95a
--- /dev/null
+++ b/images/src/org/intellij/images/options/ExternalEditorOptions.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.options;
+
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * External editor options.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface ExternalEditorOptions extends Cloneable {
+ @NonNls
+ String ATTR_PREFIX = "ExternalEditor.";
+ @NonNls
+ String ATTR_EXECUTABLE_PATH = ATTR_PREFIX + "executablePath";
+
+ String getExecutablePath();
+
+ void inject(ExternalEditorOptions options);
+
+ boolean setOption(String name, Object value);
+}
diff --git a/images/src/org/intellij/images/options/GridOptions.java b/images/src/org/intellij/images/options/GridOptions.java
new file mode 100644
index 000000000000..a428552be4a2
--- /dev/null
+++ b/images/src/org/intellij/images/options/GridOptions.java
@@ -0,0 +1,55 @@
+/*
+ * 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.options;
+
+import com.intellij.ui.JBColor;
+import org.jetbrains.annotations.NonNls;
+
+import java.awt.*;
+
+/**
+ * Grid layer options
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface GridOptions extends Cloneable {
+ @NonNls
+ String ATTR_PREFIX = "Editor.Grid.";
+ @NonNls
+ String ATTR_SHOW_DEFAULT = ATTR_PREFIX + "showDefault";
+ @NonNls
+ String ATTR_LINE_ZOOM_FACTOR = ATTR_PREFIX + "lineZoomFactor";
+ @NonNls
+ String ATTR_LINE_SPAN = ATTR_PREFIX + "lineSpan";
+ @NonNls
+ String ATTR_LINE_COLOR = ATTR_PREFIX + "lineColor";
+
+ int DEFAULT_LINE_ZOOM_FACTOR = 3;
+ int DEFAULT_LINE_SPAN = 1;
+ Color DEFAULT_LINE_COLOR = JBColor.DARK_GRAY;
+
+ boolean isShowDefault();
+
+ int getLineZoomFactor();
+
+ int getLineSpan();
+
+ Color getLineColor();
+
+ void inject(GridOptions options);
+
+ boolean setOption(String name, Object value);
+}
diff --git a/images/src/org/intellij/images/options/Options.java b/images/src/org/intellij/images/options/Options.java
new file mode 100644
index 000000000000..c6f61f08c699
--- /dev/null
+++ b/images/src/org/intellij/images/options/Options.java
@@ -0,0 +1,53 @@
+/*
+ * 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.options;
+
+import java.beans.PropertyChangeListener;
+
+/**
+ * Options for plugin.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface Options extends Cloneable {
+ EditorOptions getEditorOptions();
+
+ ExternalEditorOptions getExternalEditorOptions();
+
+ /**
+ * Option injection from other options.
+ *
+ * @param options Other options
+ */
+ void inject(Options options);
+
+ void addPropertyChangeListener(PropertyChangeListener listener);
+
+ void addPropertyChangeListener(String propertyName, PropertyChangeListener listener);
+
+ void removePropertyChangeListener(PropertyChangeListener listener);
+
+ void removePropertyChangeListener(String propertyName, PropertyChangeListener listener);
+
+ /**
+ * Set option by string representation.
+ *
+ * @param name Name of option
+ * @param value Value
+ * @return <code>true</code> if option is matched and setted.
+ */
+ boolean setOption(String name, Object value);
+}
diff --git a/images/src/org/intellij/images/options/OptionsManager.java b/images/src/org/intellij/images/options/OptionsManager.java
new file mode 100644
index 000000000000..3232bd47461d
--- /dev/null
+++ b/images/src/org/intellij/images/options/OptionsManager.java
@@ -0,0 +1,36 @@
+/*
+ * 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.options;
+
+import com.intellij.openapi.components.ServiceManager;
+
+/**
+ * Options manager.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public abstract class OptionsManager {
+ /**
+ * Return current options.
+ *
+ * @return Options
+ */
+ public abstract Options getOptions();
+
+ public static OptionsManager getInstance() {
+ return ServiceManager.getService(OptionsManager.class);
+ }
+}
diff --git a/images/src/org/intellij/images/options/TransparencyChessboardOptions.java b/images/src/org/intellij/images/options/TransparencyChessboardOptions.java
new file mode 100644
index 000000000000..d0fb55d61b16
--- /dev/null
+++ b/images/src/org/intellij/images/options/TransparencyChessboardOptions.java
@@ -0,0 +1,54 @@
+/*
+ * 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.options;
+
+import org.jetbrains.annotations.NonNls;
+
+import java.awt.*;
+
+/**
+ * Background chessboard options.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface TransparencyChessboardOptions extends Cloneable {
+ @NonNls
+ String ATTR_PREFIX = "Editor.TransparencyChessboard.";
+ @NonNls
+ String ATTR_SHOW_DEFAULT = ATTR_PREFIX + "showDefault";
+ @NonNls
+ String ATTR_CELL_SIZE = ATTR_PREFIX + "cellSize";
+ @NonNls
+ String ATTR_WHITE_COLOR = ATTR_PREFIX + "whiteColor";
+ @NonNls
+ String ATTR_BLACK_COLOR = ATTR_PREFIX + "blackColor";
+
+ int DEFAULT_CELL_SIZE = 5;
+ Color DEFAULT_WHITE_COLOR = Color.WHITE;
+ Color DEFAULT_BLACK_COLOR = Color.LIGHT_GRAY;
+
+ boolean isShowDefault();
+
+ int getCellSize();
+
+ Color getWhiteColor();
+
+ Color getBlackColor();
+
+ void inject(TransparencyChessboardOptions options);
+
+ boolean setOption(String name, Object value);
+}
diff --git a/images/src/org/intellij/images/options/ZoomOptions.java b/images/src/org/intellij/images/options/ZoomOptions.java
new file mode 100644
index 000000000000..f6a506129a0e
--- /dev/null
+++ b/images/src/org/intellij/images/options/ZoomOptions.java
@@ -0,0 +1,50 @@
+/*
+ * 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.options;
+
+import org.jetbrains.annotations.NonNls;
+
+import java.awt.*;
+
+/**
+ * Options for zooming feature.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface ZoomOptions extends Cloneable {
+ @NonNls
+ String ATTR_PREFIX = "Editor.Zoom.";
+ @NonNls
+ String ATTR_WHEEL_ZOOMING = ATTR_PREFIX + "wheelZooming";
+ @NonNls
+ String ATTR_SMART_ZOOMING = ATTR_PREFIX + "smartZooming";
+ @NonNls
+ String ATTR_PREFFERED_WIDTH = ATTR_PREFIX + "prefferedWidth";
+ @NonNls
+ String ATTR_PREFFERED_HEIGHT = ATTR_PREFIX + "prefferedHeight";
+
+ Dimension DEFAULT_PREFFERED_SIZE = new Dimension(128, 128);
+
+ boolean isWheelZooming();
+
+ boolean isSmartZooming();
+
+ Dimension getPrefferedSize();
+
+ void inject(ZoomOptions options);
+
+ boolean setOption(String name, Object value);
+}
diff --git a/images/src/org/intellij/images/options/impl/EditorOptionsImpl.java b/images/src/org/intellij/images/options/impl/EditorOptionsImpl.java
new file mode 100644
index 000000000000..a87d297d2087
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/EditorOptionsImpl.java
@@ -0,0 +1,108 @@
+/*
+ * 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.options.impl;
+
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.JDOMExternalizable;
+import com.intellij.openapi.util.WriteExternalException;
+import org.intellij.images.options.EditorOptions;
+import org.intellij.images.options.GridOptions;
+import org.intellij.images.options.TransparencyChessboardOptions;
+import org.intellij.images.options.ZoomOptions;
+import org.jdom.Element;
+
+import java.beans.PropertyChangeSupport;
+
+/**
+ * Editor options implementation.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class EditorOptionsImpl implements EditorOptions, JDOMExternalizable {
+ private final GridOptions gridOptions;
+ private final TransparencyChessboardOptions transparencyChessboardOptions;
+ private final ZoomOptions zoomOptions;
+
+ EditorOptionsImpl(PropertyChangeSupport propertyChangeSupport) {
+ gridOptions = new GridOptionsImpl(propertyChangeSupport);
+ transparencyChessboardOptions = new TransparencyChessboardOptionsImpl(propertyChangeSupport);
+ zoomOptions = new ZoomOptionsImpl(propertyChangeSupport);
+ }
+
+ public GridOptions getGridOptions() {
+ return gridOptions;
+ }
+
+ public TransparencyChessboardOptions getTransparencyChessboardOptions() {
+ return transparencyChessboardOptions;
+ }
+
+ public ZoomOptions getZoomOptions() {
+ return zoomOptions;
+ }
+
+ public EditorOptions clone() throws CloneNotSupportedException {
+ return (EditorOptions)super.clone();
+ }
+
+ public void inject(EditorOptions options) {
+ gridOptions.inject(options.getGridOptions());
+ transparencyChessboardOptions.inject(options.getTransparencyChessboardOptions());
+ zoomOptions.inject(options.getZoomOptions());
+ }
+
+ public boolean setOption(String name, Object value) {
+ return gridOptions.setOption(name, value) ||
+ transparencyChessboardOptions.setOption(name, value) ||
+ zoomOptions.setOption(name, value);
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ ((JDOMExternalizable)gridOptions).readExternal(element);
+ ((JDOMExternalizable)transparencyChessboardOptions).readExternal(element);
+ ((JDOMExternalizable)zoomOptions).readExternal(element);
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ ((JDOMExternalizable)gridOptions).writeExternal(element);
+ ((JDOMExternalizable)transparencyChessboardOptions).writeExternal(element);
+ ((JDOMExternalizable)zoomOptions).writeExternal(element);
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof EditorOptions)) {
+ return false;
+ }
+ EditorOptions otherOptions = (EditorOptions)obj;
+ GridOptions gridOptions = otherOptions.getGridOptions();
+ TransparencyChessboardOptions chessboardOptions = otherOptions.getTransparencyChessboardOptions();
+ ZoomOptions zoomOptions = otherOptions.getZoomOptions();
+ return gridOptions != null && gridOptions.equals(getGridOptions()) &&
+ chessboardOptions != null && chessboardOptions.equals(getTransparencyChessboardOptions()) &&
+ zoomOptions != null && zoomOptions.equals(getZoomOptions());
+ }
+
+ public int hashCode() {
+ int result;
+ result = (gridOptions != null ? gridOptions.hashCode() : 0);
+ result = 29 * result + (transparencyChessboardOptions != null ? transparencyChessboardOptions.hashCode() : 0);
+ result = 29 * result + (zoomOptions != null ? zoomOptions.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/images/src/org/intellij/images/options/impl/ExternalEditorOptionsImpl.java b/images/src/org/intellij/images/options/impl/ExternalEditorOptionsImpl.java
new file mode 100644
index 000000000000..358d0238bc9a
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/ExternalEditorOptionsImpl.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.options.impl;
+
+import com.intellij.openapi.util.JDOMExternalizable;
+import com.intellij.openapi.util.JDOMExternalizer;
+import org.intellij.images.options.ExternalEditorOptions;
+import org.jdom.Element;
+
+import java.beans.PropertyChangeSupport;
+
+/**
+ * External editor options.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class ExternalEditorOptionsImpl implements ExternalEditorOptions, JDOMExternalizable {
+ private final PropertyChangeSupport propertyChangeSupport;
+ private String executablePath;
+
+ public ExternalEditorOptionsImpl(PropertyChangeSupport propertyChangeSupport) {
+ this.propertyChangeSupport = propertyChangeSupport;
+ }
+
+ public String getExecutablePath() {
+ return executablePath;
+ }
+
+ void setExecutablePath(String executablePath) {
+ String oldValue = this.executablePath;
+ if (oldValue != null && !oldValue.equals(executablePath) || oldValue == null && executablePath != null) {
+ this.executablePath = executablePath;
+ propertyChangeSupport.firePropertyChange(ATTR_EXECUTABLE_PATH, oldValue, this.executablePath);
+ }
+ }
+
+ public ExternalEditorOptions clone() throws CloneNotSupportedException {
+ return (ExternalEditorOptions)super.clone();
+ }
+
+ public void inject(ExternalEditorOptions options) {
+ setExecutablePath(options.getExecutablePath());
+ }
+
+ public boolean setOption(String name, Object value) {
+ if (ATTR_EXECUTABLE_PATH.equals(name)) {
+ setExecutablePath((String) value);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ public void readExternal(Element element) {
+ executablePath = JDOMExternalizer.readString(element, ATTR_EXECUTABLE_PATH);
+ }
+
+ public void writeExternal(Element element) {
+ JDOMExternalizer.write(element, ATTR_EXECUTABLE_PATH, executablePath);
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ExternalEditorOptions)) {
+ return false;
+ }
+
+ ExternalEditorOptions otherOptions = (ExternalEditorOptions) o;
+
+ return executablePath != null ?
+ executablePath.equals(otherOptions.getExecutablePath()) :
+ otherOptions.getExecutablePath() == null;
+
+ }
+
+ public int hashCode() {
+ return executablePath != null ? executablePath.hashCode() : 0;
+ }
+}
diff --git a/images/src/org/intellij/images/options/impl/GridOptionsImpl.java b/images/src/org/intellij/images/options/impl/GridOptionsImpl.java
new file mode 100644
index 000000000000..f3f2de97f11d
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/GridOptionsImpl.java
@@ -0,0 +1,152 @@
+/*
+ * 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.options.impl;
+
+import com.intellij.openapi.util.JDOMExternalizable;
+import com.intellij.openapi.util.JDOMExternalizer;
+import org.intellij.images.options.GridOptions;
+import org.jdom.Element;
+
+import java.awt.*;
+import java.beans.PropertyChangeSupport;
+
+/**
+ * Grid options implementation.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class GridOptionsImpl implements GridOptions, JDOMExternalizable {
+ private boolean showDefault;
+ private int lineMinZoomFactor = DEFAULT_LINE_ZOOM_FACTOR;
+ private int lineSpan = DEFAULT_LINE_SPAN;
+ private Color lineColor = DEFAULT_LINE_COLOR;
+ private final PropertyChangeSupport propertyChangeSupport;
+
+ GridOptionsImpl(PropertyChangeSupport propertyChangeSupport) {
+ this.propertyChangeSupport = propertyChangeSupport;
+ }
+
+ public boolean isShowDefault() {
+ return showDefault;
+ }
+
+ public int getLineZoomFactor() {
+ return lineMinZoomFactor;
+ }
+
+ public int getLineSpan() {
+ return lineSpan;
+ }
+
+ public Color getLineColor() {
+ return lineColor;
+ }
+
+ void setShowDefault(boolean showDefault) {
+ boolean oldValue = this.showDefault;
+ if (oldValue != showDefault) {
+ this.showDefault = showDefault;
+ propertyChangeSupport.firePropertyChange(ATTR_SHOW_DEFAULT, oldValue, this.showDefault);
+ }
+ }
+
+ void setLineMinZoomFactor(int lineMinZoomFactor) {
+ int oldValue = this.lineMinZoomFactor;
+ if (oldValue != lineMinZoomFactor) {
+ this.lineMinZoomFactor = lineMinZoomFactor;
+ propertyChangeSupport.firePropertyChange(ATTR_LINE_ZOOM_FACTOR, oldValue, this.lineMinZoomFactor);
+ }
+ }
+
+ void setLineSpan(int lineSpan) {
+ int oldValue = this.lineSpan;
+ if (oldValue != lineSpan) {
+ this.lineSpan = lineSpan;
+ propertyChangeSupport.firePropertyChange(ATTR_LINE_SPAN, oldValue, this.lineSpan);
+ }
+ }
+
+ void setLineColor(Color lineColor) {
+ Color oldColor = this.lineColor;
+ if (lineColor == null) {
+ this.lineColor = DEFAULT_LINE_COLOR;
+ }
+ if (!oldColor.equals(lineColor)) {
+ this.lineColor = lineColor;
+ propertyChangeSupport.firePropertyChange(ATTR_LINE_COLOR, oldColor, this.lineColor);
+ }
+ }
+
+ public void inject(GridOptions options) {
+ setShowDefault(options.isShowDefault());
+ setLineMinZoomFactor(options.getLineZoomFactor());
+ setLineSpan(options.getLineSpan());
+ setLineColor(options.getLineColor());
+ }
+
+ public boolean setOption(String name, Object value) {
+ if (ATTR_SHOW_DEFAULT.equals(name)) {
+ setShowDefault((Boolean) value);
+ } else if (ATTR_LINE_ZOOM_FACTOR.equals(name)) {
+ setLineMinZoomFactor((Integer) value);
+ } else if (ATTR_LINE_SPAN.equals(name)) {
+ setLineSpan((Integer) value);
+ } else if (ATTR_LINE_COLOR.equals(name)) {
+ setLineColor((Color) value);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ public void readExternal(Element element) {
+ showDefault = JDOMExternalizer.readBoolean(element, ATTR_SHOW_DEFAULT);
+ lineMinZoomFactor = JDOMExternalizer.readInteger(element, ATTR_LINE_ZOOM_FACTOR, DEFAULT_LINE_ZOOM_FACTOR);
+ lineSpan = JDOMExternalizer.readInteger(element, ATTR_LINE_SPAN, DEFAULT_LINE_SPAN);
+ lineColor = JDOMExternalizerEx.readColor(element, ATTR_LINE_COLOR, DEFAULT_LINE_COLOR);
+ }
+
+ public void writeExternal(Element element) {
+ JDOMExternalizer.write(element, ATTR_SHOW_DEFAULT, showDefault);
+ JDOMExternalizer.write(element, ATTR_LINE_ZOOM_FACTOR, lineMinZoomFactor);
+ JDOMExternalizer.write(element, ATTR_LINE_SPAN, lineSpan);
+ JDOMExternalizerEx.write(element, ATTR_LINE_COLOR, lineColor);
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof GridOptions)) {
+ return false;
+ }
+
+ GridOptions otherOptions = (GridOptions) obj;
+ return lineMinZoomFactor == otherOptions.getLineZoomFactor() &&
+ lineSpan == otherOptions.getLineSpan() &&
+ showDefault == otherOptions.isShowDefault() &&
+ lineColor != null ? lineColor.equals(otherOptions.getLineColor()) : otherOptions.getLineColor() == null;
+ }
+
+ public int hashCode() {
+ int result;
+ result = (showDefault ? 1 : 0);
+ result = 29 * result + lineMinZoomFactor;
+ result = 29 * result + lineSpan;
+ result = 29 * result + (lineColor != null ? lineColor.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/images/src/org/intellij/images/options/impl/JDOMExternalizerEx.java b/images/src/org/intellij/images/options/impl/JDOMExternalizerEx.java
new file mode 100644
index 000000000000..2033c21ccdfa
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/JDOMExternalizerEx.java
@@ -0,0 +1,46 @@
+/*
+ * 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.options.impl;
+
+import com.intellij.openapi.util.JDOMExternalizer;
+import org.jdom.Element;
+
+import java.awt.*;
+
+/**
+ * Extension for {@link JDOMExternalizer}.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class JDOMExternalizerEx {
+ public static Color readColor(Element root, String name, Color defaultValue) {
+ String colorValue = JDOMExternalizer.readString(root, name);
+ if (colorValue != null) {
+ try {
+ return new Color(Integer.parseInt(colorValue, 16));
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ }
+ return defaultValue;
+ }
+
+ public static void write(Element root, String name, Color value) {
+ if (value != null) {
+ JDOMExternalizer.write(root, name, Integer.toString(value.getRGB() & 0xFFFFFF, 16));
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/options/impl/Options.form b/images/src/org/intellij/images/options/impl/Options.form
new file mode 100644
index 000000000000..197d1acf687c
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/Options.form
@@ -0,0 +1,234 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.intellij.images.options.impl.OptionsUIForm">
+ <grid id="23031" binding="contentPane" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="29" y="2" width="766" height="476"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <grid id="81843" layout-manager="GridLayoutManager" row-count="12" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <enabled value="true"/>
+ <visible value="true"/>
+ </properties>
+ <border type="none" title-resource-bundle="org/intellij/images/ImagesBundle" title-key="main.page.border.title"/>
+<clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+</clientProperties>
+ <children>
+ <component id="74001" class="javax.swing.JLabel" binding="smartZoomingWidthLabel">
+ <constraints>
+ <grid row="10" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="settings.preffered.smart.zoom.width"/>
+ </properties>
+ </component>
+ <component id="e096e" class="javax.swing.JLabel" binding="smartZoomingHeightLabel">
+ <constraints>
+ <grid row="11" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="settings.preffered.smart.zoom.height"/>
+ </properties>
+ </component>
+ <component id="5d722" class="javax.swing.JCheckBox" binding="showGrid">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="4" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="show.grid.lines"/>
+ </properties>
+ </component>
+ <component id="8092" class="javax.swing.JCheckBox" binding="showChessboard">
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="4" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="show.transparency.chessboard"/>
+ </properties>
+ </component>
+ <component id="1d481" class="javax.swing.JCheckBox" binding="wheelZooming">
+ <constraints>
+ <grid row="8" column="0" row-span="1" col-span="4" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="enable.mousewheel.zooming"/>
+ </properties>
+ </component>
+ <component id="135b7" class="javax.swing.JCheckBox" binding="smartZooming">
+ <constraints>
+ <grid row="9" column="0" row-span="1" col-span="4" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="smart.zoom"/>
+ </properties>
+ </component>
+ <component id="f13" class="javax.swing.JLabel" binding="chessboardSizeLabel">
+ <constraints>
+ <grid row="5" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="chessboard.cell.size"/>
+ </properties>
+ </component>
+ <component id="3c25" class="javax.swing.JSpinner" binding="chessboardSize">
+ <constraints>
+ <grid row="5" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="a8778" class="javax.swing.JSpinner" binding="smartZoomingWidth">
+ <constraints>
+ <grid row="10" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="57450" class="javax.swing.JSpinner" binding="smartZoomingHeight">
+ <constraints>
+ <grid row="11" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="ca32d" class="javax.swing.JLabel" binding="gridLineSpanLabel">
+ <constraints>
+ <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="show.grid.every"/>
+ </properties>
+ </component>
+ <component id="e4680" class="javax.swing.JSpinner" binding="gridLineSpan">
+ <constraints>
+ <grid row="2" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="452ab" class="javax.swing.JLabel" binding="gridLineZoomFactorlLabel">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="show.grid.zoom.limit"/>
+ </properties>
+ </component>
+ <component id="ec3c4" class="javax.swing.JSpinner" binding="gridLineZoomFactor">
+ <constraints>
+ <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="acb45" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="1" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value=""/>
+ </properties>
+ </component>
+ <hspacer id="ac4d1">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
+ <minimum-size width="22" height="-1"/>
+ <maximum-size width="22" height="-1"/>
+ </grid>
+ </constraints>
+ </hspacer>
+ <component id="e7c83" class="javax.swing.JLabel" binding="chessboardWhiteColorLabel">
+ <constraints>
+ <grid row="6" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="white.cell.color"/>
+ </properties>
+ </component>
+ <component id="5a0c4" class="javax.swing.JLabel" binding="chessboardBlackColorLabel">
+ <constraints>
+ <grid row="7" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="black.cell.color"/>
+ </properties>
+ </component>
+ <component id="b2df3" class="com.intellij.ui.ColorPanel" binding="chessboardWhiteColor">
+ <constraints>
+ <grid row="6" column="2" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="e12c2" class="com.intellij.ui.ColorPanel" binding="chessboardBlackColor">
+ <constraints>
+ <grid row="7" column="2" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="729f9" class="javax.swing.JLabel" binding="gridLineColorLabel">
+ <constraints>
+ <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="grid.line.color"/>
+ </properties>
+ </component>
+ <component id="61a7d" class="com.intellij.ui.ColorPanel" binding="gridLineColor">
+ <constraints>
+ <grid row="3" column="2" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ <hspacer id="e0712">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </hspacer>
+ <vspacer id="8c4aa">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ <grid id="f5eaa" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none" title-resource-bundle="org/intellij/images/ImagesBundle" title-key="external.editor.border.title"/>
+<clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+</clientProperties>
+ <children>
+ <component id="55972" class="javax.swing.JLabel" binding="externalEditorLabel">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="org/intellij/images/ImagesBundle" key="external.editor.executable.path"/>
+ </properties>
+ </component>
+ <component id="8e176" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="externalEditorPath">
+ <constraints>
+ <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="3" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <hspacer id="4f642">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
+ <minimum-size width="22" height="-1"/>
+ <maximum-size width="22" height="-1"/>
+ </grid>
+ </constraints>
+ </hspacer>
+ </children>
+ </grid>
+ </children>
+ </grid>
+</form>
diff --git a/images/src/org/intellij/images/options/impl/OptionsConfigurabe.java b/images/src/org/intellij/images/options/impl/OptionsConfigurabe.java
new file mode 100644
index 000000000000..0be6b07a81cd
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/OptionsConfigurabe.java
@@ -0,0 +1,109 @@
+/*
+ * 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.options.impl;
+
+import com.intellij.openapi.options.BaseConfigurableWithChangeSupport;
+import com.intellij.openapi.options.SearchableConfigurable;
+import com.intellij.openapi.options.ShowSettingsUtil;
+import com.intellij.openapi.project.Project;
+import org.intellij.images.ImagesBundle;
+import org.intellij.images.options.Options;
+import org.intellij.images.options.OptionsManager;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+/**
+ * Configurable for Options.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public final class OptionsConfigurabe extends BaseConfigurableWithChangeSupport implements SearchableConfigurable, PropertyChangeListener {
+ private static final String DISPLAY_NAME = ImagesBundle.message("settings.page.name");
+ private OptionsUIForm uiForm;
+
+ public String getDisplayName() {
+ return DISPLAY_NAME;
+ }
+
+ public String getHelpTopic() {
+ return "preferences.images";
+ }
+
+ public JComponent createComponent() {
+ if (uiForm == null) {
+ uiForm = new OptionsUIForm();
+ Options options = OptionsManager.getInstance().getOptions();
+ options.addPropertyChangeListener(this);
+ uiForm.getOptions().inject(options);
+ uiForm.updateUI();
+ uiForm.getOptions().addPropertyChangeListener(this);
+ setModified(false);
+ }
+ return uiForm.getContentPane();
+ }
+
+ public void apply() {
+ if (uiForm != null) {
+ Options options = OptionsManager.getInstance().getOptions();
+ options.inject(uiForm.getOptions());
+ }
+ }
+
+ public void reset() {
+ if (uiForm != null) {
+ Options options = OptionsManager.getInstance().getOptions();
+ uiForm.getOptions().inject(options);
+ uiForm.updateUI();
+ }
+ }
+
+ public void disposeUIResources() {
+ if (uiForm != null) {
+ Options options = OptionsManager.getInstance().getOptions();
+ options.removePropertyChangeListener(this);
+ uiForm.getOptions().removePropertyChangeListener(this);
+ uiForm = null;
+ }
+ }
+
+ public void propertyChange(PropertyChangeEvent evt) {
+ Options options = OptionsManager.getInstance().getOptions();
+ Options uiOptions = uiForm.getOptions();
+
+ setModified(!options.equals(uiOptions));
+ }
+
+ public static void show(Project project) {
+ final ShowSettingsUtil util = ShowSettingsUtil.getInstance();
+ util.editConfigurable(project, new OptionsConfigurabe());
+ }
+
+ @NotNull
+ @NonNls
+ public String getId() {
+ return "Images";
+ }
+
+ @Nullable
+ public Runnable enableSearch(String option) {
+ return null;
+ }
+}
diff --git a/images/src/org/intellij/images/options/impl/OptionsImpl.java b/images/src/org/intellij/images/options/impl/OptionsImpl.java
new file mode 100644
index 000000000000..17c95916bf5d
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/OptionsImpl.java
@@ -0,0 +1,112 @@
+/*
+ * 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.options.impl;
+
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.JDOMExternalizable;
+import com.intellij.openapi.util.WriteExternalException;
+import org.intellij.images.options.EditorOptions;
+import org.intellij.images.options.ExternalEditorOptions;
+import org.intellij.images.options.Options;
+import org.jdom.Element;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+/**
+ * Default options implementation.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class OptionsImpl implements Options, JDOMExternalizable {
+ /**
+ * Property change support (from injection)
+ */
+ private final PropertyChangeSupport propertyChangeSupport;
+
+ private final EditorOptions editorOptions;
+ private final ExternalEditorOptions externalEditorOptions;
+
+ OptionsImpl() {
+ propertyChangeSupport = new PropertyChangeSupport(this);
+ editorOptions = new EditorOptionsImpl(propertyChangeSupport);
+ externalEditorOptions = new ExternalEditorOptionsImpl(propertyChangeSupport);
+ }
+
+ public EditorOptions getEditorOptions() {
+ return editorOptions;
+ }
+
+ public ExternalEditorOptions getExternalEditorOptions() {
+ return externalEditorOptions;
+ }
+
+ public void inject(Options options) {
+ editorOptions.inject(options.getEditorOptions());
+ externalEditorOptions.inject(options.getExternalEditorOptions());
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ propertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+ propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ propertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+ propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
+ }
+
+ public boolean setOption(String name, Object value) {
+ return editorOptions.setOption(name, value) || externalEditorOptions.setOption(name, value);
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ ((JDOMExternalizable)editorOptions).readExternal(element);
+ ((JDOMExternalizable)externalEditorOptions).readExternal(element);
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ ((JDOMExternalizable)editorOptions).writeExternal(element);
+ ((JDOMExternalizable)externalEditorOptions).writeExternal(element);
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Options)) {
+ return false;
+ }
+ Options otherOptions = (Options)obj;
+ EditorOptions editorOptions = otherOptions.getEditorOptions();
+ ExternalEditorOptions externalEditorOptions = otherOptions.getExternalEditorOptions();
+ return editorOptions != null && editorOptions.equals(getEditorOptions()) &&
+ externalEditorOptions != null && externalEditorOptions.equals(getExternalEditorOptions());
+ }
+
+ public int hashCode() {
+ int result;
+ result = (editorOptions != null ? editorOptions.hashCode() : 0);
+ result = 29 * result + (externalEditorOptions != null ? externalEditorOptions.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/images/src/org/intellij/images/options/impl/OptionsManagerImpl.java b/images/src/org/intellij/images/options/impl/OptionsManagerImpl.java
new file mode 100644
index 000000000000..f1a1774b80f0
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/OptionsManagerImpl.java
@@ -0,0 +1,66 @@
+/*
+ * 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.options.impl;
+
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.RoamingTypeDisabled;
+import com.intellij.openapi.util.WriteExternalException;
+import org.intellij.images.options.Options;
+import org.intellij.images.options.OptionsManager;
+import org.jdom.Element;
+
+/**
+ * Options configurable manager.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+@State(
+ name = "Images.OptionsManager",
+ storages = {
+ @Storage(file = StoragePathMacros.APP_CONFIG + "/images.support.xml")
+ }
+)
+final class OptionsManagerImpl extends OptionsManager implements PersistentStateComponent<Element>, RoamingTypeDisabled {
+ private final OptionsImpl options = new OptionsImpl();
+
+ public Options getOptions() {
+ return options;
+ }
+
+ public Element getState() {
+ Element element = new Element("state");
+ try {
+ options.writeExternal(element);
+ }
+ catch (WriteExternalException e) {
+ throw new RuntimeException(e);
+ }
+ return element;
+ }
+
+ public void loadState(final Element state) {
+ try {
+ options.readExternal(state);
+ }
+ catch (InvalidDataException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/options/impl/OptionsUIForm.java b/images/src/org/intellij/images/options/impl/OptionsUIForm.java
new file mode 100644
index 000000000000..9f945bee3ca1
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/OptionsUIForm.java
@@ -0,0 +1,261 @@
+/*
+ * 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.options.impl;
+
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.util.NullableComputable;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.ColorPanel;
+import com.intellij.ui.DocumentAdapter;
+import com.intellij.util.Consumer;
+import org.intellij.images.ImagesBundle;
+import org.intellij.images.options.*;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Position;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.List;
+
+/**
+ * Options UI form bean.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class OptionsUIForm {
+ private JPanel contentPane;
+ private JCheckBox showGrid;
+ private JLabel gridLineZoomFactorlLabel;
+ private JSpinner gridLineZoomFactor;
+ private JLabel gridLineSpanLabel;
+ private JSpinner gridLineSpan;
+ private JCheckBox showChessboard;
+ private JSpinner chessboardSize;
+ private JLabel chessboardSizeLabel;
+ private JCheckBox wheelZooming;
+ private JCheckBox smartZooming;
+ private JSpinner smartZoomingWidth;
+ private JLabel smartZoomingWidthLabel;
+ private JSpinner smartZoomingHeight;
+ private JLabel smartZoomingHeightLabel;
+ private JLabel gridLineColorLabel;
+ private ColorPanel gridLineColor;
+ private JLabel chessboardWhiteColorLabel;
+ private JLabel chessboardBlackColorLabel;
+ private ColorPanel chessboardBlackColor;
+ private ColorPanel chessboardWhiteColor;
+ private JLabel externalEditorLabel;
+ private TextFieldWithBrowseButton externalEditorPath;
+
+ // Options
+ private final Options options = new OptionsImpl();
+
+ OptionsUIForm() {
+
+ wheelZooming.setText(ImagesBundle.message("enable.mousewheel.zooming", SystemInfo.isMac ? "Cmd" : "Ctrl"));
+
+ // Setup labels
+ gridLineZoomFactorlLabel.setLabelFor(gridLineZoomFactor);
+ gridLineSpanLabel.setLabelFor(gridLineSpan);
+ chessboardSizeLabel.setLabelFor(chessboardSize);
+ smartZoomingWidthLabel.setLabelFor(smartZoomingWidth);
+ smartZoomingHeightLabel.setLabelFor(smartZoomingHeight);
+ gridLineColorLabel.setLabelFor(gridLineColor);
+ chessboardWhiteColorLabel.setLabelFor(chessboardWhiteColor);
+ chessboardBlackColorLabel.setLabelFor(chessboardBlackColor);
+ externalEditorLabel.setLabelFor(externalEditorPath);
+
+ // Setup listeners for enabling and disabling linked checkbox groups
+ smartZooming.addItemListener(new LinkEnabledListener(new JComponent[]{
+ smartZoomingHeightLabel,
+ smartZoomingHeight,
+ smartZoomingWidthLabel,
+ smartZoomingWidth,
+ }));
+ // Setup spinners models
+ gridLineZoomFactor.setModel(new SpinnerNumberModel(GridOptions.DEFAULT_LINE_ZOOM_FACTOR, 2, 8, 1));
+ gridLineSpan.setModel(new SpinnerNumberModel(GridOptions.DEFAULT_LINE_SPAN, 1, 100, 1));
+ chessboardSize.setModel(new SpinnerNumberModel(TransparencyChessboardOptions.DEFAULT_CELL_SIZE, 1, 100, 1));
+ smartZoomingWidth.setModel(new SpinnerNumberModel(ZoomOptions.DEFAULT_PREFFERED_SIZE.width, 1, 9999, 1));
+ smartZoomingHeight.setModel(new SpinnerNumberModel(ZoomOptions.DEFAULT_PREFFERED_SIZE.height, 1, 9999, 1));
+
+ // Setup listeners for chnages
+ showGrid.addItemListener(new CheckboxOptionsListener(GridOptions.ATTR_SHOW_DEFAULT));
+ gridLineZoomFactor.addChangeListener(new SpinnerOptionsListener(GridOptions.ATTR_LINE_ZOOM_FACTOR));
+ gridLineSpan.addChangeListener(new SpinnerOptionsListener(GridOptions.ATTR_LINE_SPAN));
+ showChessboard.addItemListener(new CheckboxOptionsListener(TransparencyChessboardOptions.ATTR_SHOW_DEFAULT));
+ chessboardSize.addChangeListener(new SpinnerOptionsListener(TransparencyChessboardOptions.ATTR_CELL_SIZE));
+ wheelZooming.addItemListener(new CheckboxOptionsListener(ZoomOptions.ATTR_WHEEL_ZOOMING));
+ smartZooming.addItemListener(new CheckboxOptionsListener(ZoomOptions.ATTR_SMART_ZOOMING));
+ smartZoomingWidth.addChangeListener(new SpinnerOptionsListener(ZoomOptions.ATTR_PREFFERED_WIDTH));
+ smartZoomingHeight.addChangeListener(new SpinnerOptionsListener(ZoomOptions.ATTR_PREFFERED_HEIGHT));
+ gridLineColor.addActionListener(new ColorOptionsListener(GridOptions.ATTR_LINE_COLOR));
+ chessboardWhiteColor.addActionListener(new ColorOptionsListener(TransparencyChessboardOptions.ATTR_WHITE_COLOR));
+ chessboardBlackColor.addActionListener(new ColorOptionsListener(TransparencyChessboardOptions.ATTR_BLACK_COLOR));
+ externalEditorPath.getTextField().getDocument()
+ .addDocumentListener(new TextDocumentOptionsListener(ExternalEditorOptions.ATTR_EXECUTABLE_PATH));
+
+ externalEditorPath.addActionListener(new ExternalEditorPathActionListener());
+
+ updateUI();
+ }
+
+ public JPanel getContentPane() {
+ return contentPane;
+ }
+
+ private static class LinkEnabledListener implements ItemListener {
+ private final JComponent[] children;
+
+ LinkEnabledListener(JComponent[] children) {
+ this.children = children.clone();
+ }
+
+ public void itemStateChanged(ItemEvent e) {
+ setSelected(e.getStateChange() == ItemEvent.SELECTED);
+ }
+
+ private void setSelected(boolean selected) {
+ for (JComponent component : children) {
+ component.setEnabled(selected);
+ }
+ }
+ }
+
+ public Options getOptions() {
+ return options;
+ }
+
+ public void updateUI() {
+ // Grid options
+ EditorOptions editorOptions = options.getEditorOptions();
+ ExternalEditorOptions externalEditorOptions = options.getExternalEditorOptions();
+
+ GridOptions gridOptions = editorOptions.getGridOptions();
+ showGrid.setSelected(gridOptions.isShowDefault());
+ gridLineZoomFactor.setValue(gridOptions.getLineZoomFactor());
+ gridLineSpan.setValue(gridOptions.getLineSpan());
+ gridLineColor.setSelectedColor(gridOptions.getLineColor());
+ TransparencyChessboardOptions transparencyChessboardOptions = editorOptions.getTransparencyChessboardOptions();
+ showChessboard.setSelected(transparencyChessboardOptions.isShowDefault());
+ chessboardSize.setValue(transparencyChessboardOptions.getCellSize());
+ chessboardWhiteColor.setSelectedColor(transparencyChessboardOptions.getWhiteColor());
+ chessboardBlackColor.setSelectedColor(transparencyChessboardOptions.getBlackColor());
+ ZoomOptions zoomOptions = editorOptions.getZoomOptions();
+ wheelZooming.setSelected(zoomOptions.isWheelZooming());
+ smartZooming.setSelected(zoomOptions.isSmartZooming());
+ Dimension prefferedSize = zoomOptions.getPrefferedSize();
+ smartZoomingWidth.setValue(prefferedSize.width);
+ smartZoomingHeight.setValue(prefferedSize.height);
+ externalEditorPath.setText(externalEditorOptions.getExecutablePath());
+ }
+
+ private final class CheckboxOptionsListener implements ItemListener {
+ private final String name;
+
+ private CheckboxOptionsListener(String name) {
+ this.name = name;
+ }
+
+ @SuppressWarnings({"UnnecessaryBoxing"})
+ public void itemStateChanged(ItemEvent e) {
+ options.setOption(name, Boolean.valueOf(ItemEvent.SELECTED == e.getStateChange()));
+ }
+ }
+
+ private final class SpinnerOptionsListener implements ChangeListener {
+ private final String name;
+
+ private SpinnerOptionsListener(String name) {
+ this.name = name;
+ }
+
+ public void stateChanged(ChangeEvent e) {
+ JSpinner source = (JSpinner)e.getSource();
+ options.setOption(name, source.getValue());
+ }
+ }
+
+ private final class ColorOptionsListener implements ActionListener {
+ private final String name;
+
+ private ColorOptionsListener(String name) {
+ this.name = name;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ ColorPanel source = (ColorPanel)e.getSource();
+ options.setOption(name, source.getSelectedColor());
+ }
+ }
+
+ private final class TextDocumentOptionsListener extends DocumentAdapter {
+ private final String name;
+
+ public TextDocumentOptionsListener(String name) {
+ this.name = name;
+ }
+
+ protected void textChanged(DocumentEvent documentEvent) {
+ Document document = documentEvent.getDocument();
+ Position startPosition = document.getStartPosition();
+ try {
+ options.setOption(name, document.getText(startPosition.getOffset(), document.getLength()));
+ }
+ catch (BadLocationException e) {
+ // Ignore
+ }
+ }
+ }
+
+ private final class ExternalEditorPathActionListener implements ActionListener {
+ public void actionPerformed(ActionEvent e) {
+ Application application = ApplicationManager.getApplication();
+ VirtualFile previous = application.runWriteAction(new NullableComputable<VirtualFile>() {
+ public VirtualFile compute() {
+ final String path = FileUtil.toSystemIndependentName(externalEditorPath.getText());
+ return LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
+ }
+ });
+ FileChooserDescriptor fileDescriptor = new FileChooserDescriptor(true, SystemInfo.isMac, false, false, false, false);
+ fileDescriptor.setShowFileSystemRoots(true);
+ fileDescriptor.setTitle(ImagesBundle.message("select.external.executable.title"));
+ fileDescriptor.setDescription(ImagesBundle.message("select.external.executable.message"));
+ FileChooser.chooseFiles(fileDescriptor, null, previous, new Consumer<List<VirtualFile>>() {
+ @Override
+ public void consume(final List<VirtualFile> files) {
+ String path = files.get(0).getPath();
+ externalEditorPath.setText(path);
+ }
+ });
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/options/impl/TransparencyChessboardOptionsImpl.java b/images/src/org/intellij/images/options/impl/TransparencyChessboardOptionsImpl.java
new file mode 100644
index 000000000000..9952ba495046
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/TransparencyChessboardOptionsImpl.java
@@ -0,0 +1,161 @@
+/*
+ * 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.options.impl;
+
+import com.intellij.openapi.util.JDOMExternalizable;
+import com.intellij.openapi.util.JDOMExternalizer;
+import org.intellij.images.options.TransparencyChessboardOptions;
+import org.jdom.Element;
+
+import java.awt.*;
+import java.beans.PropertyChangeSupport;
+
+/**
+ * Background options implementation.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class TransparencyChessboardOptionsImpl implements TransparencyChessboardOptions, JDOMExternalizable {
+ private boolean showDefault = true;
+ private int cellSize = DEFAULT_CELL_SIZE;
+ private Color whiteColor = DEFAULT_WHITE_COLOR;
+ private Color blackColor = DEFAULT_BLACK_COLOR;
+ private final PropertyChangeSupport propertyChangeSupport;
+
+ TransparencyChessboardOptionsImpl(PropertyChangeSupport propertyChangeSupport) {
+ this.propertyChangeSupport = propertyChangeSupport;
+ }
+
+ public boolean isShowDefault() {
+ return showDefault;
+ }
+
+ public int getCellSize() {
+ return cellSize;
+ }
+
+ public Color getWhiteColor() {
+ return whiteColor;
+ }
+
+ public Color getBlackColor() {
+ return blackColor;
+ }
+
+ void setShowDefault(boolean showDefault) {
+ boolean oldValue = this.showDefault;
+ if (oldValue != showDefault) {
+ this.showDefault = showDefault;
+ propertyChangeSupport.firePropertyChange(ATTR_SHOW_DEFAULT, oldValue, this.showDefault);
+ }
+ }
+
+ void setCellSize(int cellSize) {
+ int oldValue = this.cellSize;
+ if (oldValue != cellSize) {
+ this.cellSize = cellSize;
+ propertyChangeSupport.firePropertyChange(ATTR_CELL_SIZE, oldValue, this.cellSize);
+ }
+ }
+
+ void setWhiteColor(Color whiteColor) {
+ Color oldValue = this.whiteColor;
+ if (whiteColor == null) {
+ this.whiteColor = DEFAULT_WHITE_COLOR;
+ }
+ if (!oldValue.equals(whiteColor)) {
+ this.whiteColor = whiteColor;
+ propertyChangeSupport.firePropertyChange(ATTR_WHITE_COLOR, oldValue, this.whiteColor);
+ }
+ }
+
+ void setBlackColor(Color blackColor) {
+ Color oldValue = this.blackColor;
+ if (blackColor == null) {
+ blackColor = DEFAULT_BLACK_COLOR;
+ }
+ if (!oldValue.equals(blackColor)) {
+ this.blackColor = blackColor;
+ propertyChangeSupport.firePropertyChange(ATTR_BLACK_COLOR, oldValue, this.blackColor);
+ }
+ }
+
+ public void inject(TransparencyChessboardOptions options) {
+ setShowDefault(options.isShowDefault());
+ setCellSize(options.getCellSize());
+ setWhiteColor(options.getWhiteColor());
+ setBlackColor(options.getBlackColor());
+ }
+
+ public boolean setOption(String name, Object value) {
+ if (ATTR_SHOW_DEFAULT.equals(name)) {
+ setShowDefault((Boolean)value);
+ } else if (ATTR_CELL_SIZE.equals(name)) {
+ setCellSize((Integer)value);
+ } else if (ATTR_WHITE_COLOR.equals(name)) {
+ setWhiteColor((Color)value);
+ } else if (ATTR_BLACK_COLOR.equals(name)) {
+ setBlackColor((Color)value);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ public void readExternal(Element element) {
+ setShowDefault(JDOMExternalizer.readBoolean(element, ATTR_SHOW_DEFAULT));
+ setCellSize(JDOMExternalizer.readInteger(element, ATTR_CELL_SIZE, DEFAULT_CELL_SIZE));
+ setWhiteColor(JDOMExternalizerEx.readColor(element, ATTR_WHITE_COLOR, DEFAULT_WHITE_COLOR));
+ setBlackColor(JDOMExternalizerEx.readColor(element, ATTR_BLACK_COLOR, DEFAULT_BLACK_COLOR));
+ }
+
+ public void writeExternal(Element element) {
+ JDOMExternalizer.write(element, ATTR_SHOW_DEFAULT, showDefault);
+ JDOMExternalizer.write(element, ATTR_CELL_SIZE, cellSize);
+ JDOMExternalizerEx.write(element, ATTR_WHITE_COLOR, whiteColor);
+ JDOMExternalizerEx.write(element, ATTR_BLACK_COLOR, blackColor);
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TransparencyChessboardOptions)) {
+ return false;
+ }
+
+ TransparencyChessboardOptions otherOptions = (TransparencyChessboardOptions)o;
+
+ return cellSize == otherOptions.getCellSize() &&
+ showDefault == otherOptions.isShowDefault() &&
+ (blackColor != null ?
+ blackColor.equals(otherOptions.getBlackColor()) :
+ otherOptions.getBlackColor() == null) &&
+ (whiteColor != null ?
+ whiteColor.equals(otherOptions.getWhiteColor()) :
+ otherOptions.getWhiteColor() == null);
+
+ }
+
+ public int hashCode() {
+ int result;
+ result = (showDefault ? 1 : 0);
+ result = 29 * result + cellSize;
+ result = 29 * result + (whiteColor != null ? whiteColor.hashCode() : 0);
+ result = 29 * result + (blackColor != null ? blackColor.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/images/src/org/intellij/images/options/impl/ZoomOptionsImpl.java b/images/src/org/intellij/images/options/impl/ZoomOptionsImpl.java
new file mode 100644
index 000000000000..97dcd6b064e5
--- /dev/null
+++ b/images/src/org/intellij/images/options/impl/ZoomOptionsImpl.java
@@ -0,0 +1,155 @@
+/*
+ * 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.options.impl;
+
+import com.intellij.openapi.util.JDOMExternalizable;
+import com.intellij.openapi.util.JDOMExternalizer;
+import org.intellij.images.options.ZoomOptions;
+import org.jdom.Element;
+
+import java.awt.*;
+import java.beans.PropertyChangeSupport;
+
+/**
+ * Zoom options implementation.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class ZoomOptionsImpl implements ZoomOptions, JDOMExternalizable {
+ private boolean wheelZooming;
+ private boolean smartZooming = true;
+ private int prefferedWidth = DEFAULT_PREFFERED_SIZE.width;
+ private int prefferedHeight = DEFAULT_PREFFERED_SIZE.height;
+ private final PropertyChangeSupport propertyChangeSupport;
+
+ ZoomOptionsImpl(PropertyChangeSupport propertyChangeSupport) {
+ this.propertyChangeSupport = propertyChangeSupport;
+ }
+
+ public boolean isWheelZooming() {
+ return wheelZooming;
+ }
+
+ public boolean isSmartZooming() {
+ return smartZooming;
+ }
+
+ public Dimension getPrefferedSize() {
+ return new Dimension(prefferedWidth, prefferedHeight);
+ }
+
+ void setWheelZooming(boolean wheelZooming) {
+ boolean oldValue = this.wheelZooming;
+ if (oldValue != wheelZooming) {
+ this.wheelZooming = wheelZooming;
+ propertyChangeSupport.firePropertyChange(ATTR_WHEEL_ZOOMING, oldValue, this.wheelZooming);
+ }
+ }
+
+ void setSmartZooming(boolean smartZooming) {
+ boolean oldValue = this.smartZooming;
+ if (oldValue != smartZooming) {
+ this.smartZooming = smartZooming;
+ propertyChangeSupport.firePropertyChange(ATTR_SMART_ZOOMING, oldValue, this.smartZooming);
+ }
+ }
+
+ void setPrefferedSize(Dimension prefferedSize) {
+ if (prefferedSize == null) {
+ prefferedSize = DEFAULT_PREFFERED_SIZE;
+ }
+ setPrefferedWidth(prefferedSize.width);
+ setPrefferedHeight(prefferedSize.height);
+ }
+
+ void setPrefferedWidth(int prefferedWidth) {
+ int oldValue = this.prefferedWidth;
+ if (oldValue != prefferedWidth) {
+ this.prefferedWidth = prefferedWidth;
+ propertyChangeSupport.firePropertyChange(ATTR_PREFFERED_WIDTH, oldValue, this.prefferedWidth);
+ }
+ }
+
+ void setPrefferedHeight(int prefferedHeight) {
+ int oldValue = this.prefferedHeight;
+ if (oldValue != prefferedHeight) {
+ this.prefferedHeight = prefferedHeight;
+ propertyChangeSupport.firePropertyChange(ATTR_PREFFERED_HEIGHT, oldValue, this.prefferedHeight);
+ }
+ }
+
+ public void inject(ZoomOptions options) {
+ setWheelZooming(options.isWheelZooming());
+ setSmartZooming(options.isSmartZooming());
+ setPrefferedSize(options.getPrefferedSize());
+ }
+
+ public boolean setOption(String name, Object value) {
+ if (ATTR_WHEEL_ZOOMING.equals(name)) {
+ setWheelZooming((Boolean)value);
+ } else if (ATTR_SMART_ZOOMING.equals(name)) {
+ setSmartZooming((Boolean)value);
+ } else if (ATTR_PREFFERED_WIDTH.equals(name)) {
+ setPrefferedWidth((Integer)value);
+ } else if (ATTR_PREFFERED_HEIGHT.equals(name)) {
+ setPrefferedHeight((Integer)value);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ public void readExternal(Element element) {
+ setWheelZooming(JDOMExternalizer.readBoolean(element, ATTR_WHEEL_ZOOMING));
+ setSmartZooming(JDOMExternalizer.readBoolean(element, ATTR_SMART_ZOOMING));
+ setPrefferedWidth(JDOMExternalizer.readInteger(element, ATTR_PREFFERED_WIDTH, DEFAULT_PREFFERED_SIZE.width));
+ setPrefferedHeight(JDOMExternalizer.readInteger(element, ATTR_PREFFERED_HEIGHT, DEFAULT_PREFFERED_SIZE.height));
+ }
+
+ public void writeExternal(Element element) {
+ JDOMExternalizer.write(element, ATTR_WHEEL_ZOOMING, wheelZooming);
+ JDOMExternalizer.write(element, ATTR_SMART_ZOOMING, smartZooming);
+ JDOMExternalizer.write(element, ATTR_PREFFERED_WIDTH, prefferedWidth);
+ JDOMExternalizer.write(element, ATTR_PREFFERED_HEIGHT, prefferedHeight);
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ZoomOptions)) {
+ return false;
+ }
+
+ ZoomOptions otherOptions = (ZoomOptions)obj;
+
+ Dimension prefferedSize = otherOptions.getPrefferedSize();
+ return prefferedSize != null && prefferedHeight == prefferedSize.height &&
+ prefferedWidth == prefferedSize.width &&
+ smartZooming == otherOptions.isSmartZooming() &&
+ wheelZooming == otherOptions.isWheelZooming();
+
+ }
+
+ public int hashCode() {
+ int result;
+ result = (wheelZooming ? 1 : 0);
+ result = 29 * result + (smartZooming ? 1 : 0);
+ result = 29 * result + prefferedWidth;
+ result = 29 * result + prefferedHeight;
+ return result;
+ }
+}
diff --git a/images/src/org/intellij/images/thumbnail/ThumbnailManager.java b/images/src/org/intellij/images/thumbnail/ThumbnailManager.java
new file mode 100644
index 000000000000..64314d31251d
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/ThumbnailManager.java
@@ -0,0 +1,41 @@
+/*
+ * 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.thumbnail;
+
+import org.jetbrains.annotations.NotNull;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.components.ServiceManager;
+
+/**
+ * Thumbnail manager.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public abstract class ThumbnailManager {
+
+ public static ThumbnailManager getManager(final Project project) {
+ return ServiceManager.getService(project, ThumbnailManager.class);
+ }
+
+ /**
+ * Create thumbnail view
+ *
+ * @return Return thumbnail view
+ */
+ @NotNull
+ public abstract ThumbnailView getThumbnailView();
+
+}
diff --git a/images/src/org/intellij/images/thumbnail/ThumbnailView.java b/images/src/org/intellij/images/thumbnail/ThumbnailView.java
new file mode 100644
index 000000000000..cf0fc5d37b98
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/ThumbnailView.java
@@ -0,0 +1,75 @@
+/*
+ * 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.thumbnail;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.DataKey;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.intellij.images.ImagesBundle;
+import org.intellij.images.ui.ImageComponentDecorator;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Thumbnail thumbnail is a component with thumbnails for a set of {@link com.intellij.openapi.vfs.VirtualFile}.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface ThumbnailView extends Disposable, ImageComponentDecorator {
+ DataKey<ThumbnailView> DATA_KEY = DataKey.create(ThumbnailView.class.getName());
+
+ String TOOLWINDOW_ID = ImagesBundle.message("thumbnails.toolwindow.name");
+
+ @NotNull
+ Project getProject();
+
+ /**
+ * Add virtual files to view
+ *
+ * @param root Root
+ */
+ void setRoot(@NotNull VirtualFile root);
+
+ /**
+ * Return current root
+ *
+ * @return Current root
+ */
+ VirtualFile getRoot();
+
+ boolean isRecursive();
+
+ void setRecursive(boolean recursive);
+
+ void setSelected(@NotNull VirtualFile file, boolean selected);
+
+ boolean isSelected(@NotNull VirtualFile file);
+
+ @NotNull
+ VirtualFile[] getSelection();
+
+ /**
+ * Scroll to selection. If ToolWindow is not active, then
+ * it will perform activatation before scroll.
+ */
+ void scrollToSelection();
+
+ void setVisible(boolean visible);
+
+ boolean isVisible();
+
+ void activate();
+}
diff --git a/images/src/org/intellij/images/thumbnail/actionSystem/ThumbnailViewActionUtil.java b/images/src/org/intellij/images/thumbnail/actionSystem/ThumbnailViewActionUtil.java
new file mode 100644
index 000000000000..bf381dff43ee
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/actionSystem/ThumbnailViewActionUtil.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.thumbnail.actionSystem;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import org.intellij.images.thumbnail.ThumbnailView;
+
+/**
+ * Thumbnail view actions utility.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public final class ThumbnailViewActionUtil {
+ private ThumbnailViewActionUtil() {
+ }
+
+ /**
+ * Extract current thumbnail view from event context.
+ *
+ * @param e Action event
+ * @return Current {@link org.intellij.images.thumbnail.ThumbnailView} or <code>null</code>
+ */
+ public static ThumbnailView getVisibleThumbnailView(AnActionEvent e) {
+ ThumbnailView thumbnailView = getThumbnailView(e);
+ if (thumbnailView != null && thumbnailView.isVisible()) {
+ return thumbnailView;
+ }
+ return null;
+ }
+
+ public static ThumbnailView getThumbnailView(AnActionEvent e) {
+ return ThumbnailView.DATA_KEY.getData(e.getDataContext());
+ }
+
+ /**
+ * Enable or disable current action from event.
+ *
+ * @param e Action event
+ * @return Enabled value
+ */
+ public static boolean setEnabled(AnActionEvent e) {
+ ThumbnailView thumbnailView = getVisibleThumbnailView(e);
+ Presentation presentation = e.getPresentation();
+ presentation.setEnabled(thumbnailView != null);
+ return presentation.isEnabled();
+ }
+}
diff --git a/images/src/org/intellij/images/thumbnail/actionSystem/ThumbnailViewActions.java b/images/src/org/intellij/images/thumbnail/actionSystem/ThumbnailViewActions.java
new file mode 100644
index 000000000000..7ae268c1ea2c
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/actionSystem/ThumbnailViewActions.java
@@ -0,0 +1,29 @@
+/*
+ * 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.thumbnail.actionSystem;
+
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * Editor actions.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public interface ThumbnailViewActions {
+ @NonNls String GROUP_POPUP = "Images.ThumbnailsPopupMenu";
+ @NonNls String GROUP_TOOLBAR = "Images.ThumbnailsToolbar";
+ @NonNls String ACTION_PLACE = "Images.Thumbnails";
+}
diff --git a/images/src/org/intellij/images/thumbnail/actions/EnterAction.java b/images/src/org/intellij/images/thumbnail/actions/EnterAction.java
new file mode 100644
index 000000000000..b608b82f97c7
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/actions/EnterAction.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.thumbnail.actions;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import org.intellij.images.thumbnail.ThumbnailView;
+import org.intellij.images.thumbnail.actionSystem.ThumbnailViewActionUtil;
+import org.intellij.images.fileTypes.ImageFileTypeManager;
+
+/**
+ * Level up to browse images.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public final class EnterAction extends AnAction {
+ public void actionPerformed(AnActionEvent e) {
+ ThumbnailView view = ThumbnailViewActionUtil.getVisibleThumbnailView(e);
+ if (view != null) {
+ VirtualFile[] selection = view.getSelection();
+ if (selection.length == 1 && selection[0].isDirectory()) {
+ view.setRoot(selection[0]);
+ } else if (selection.length > 0) {
+ FileEditorManager fileEditorManager = FileEditorManager.getInstance(view.getProject());
+ ImageFileTypeManager typeManager = ImageFileTypeManager.getInstance();
+ for (VirtualFile file : selection) {
+ if (typeManager.isImage(file)) {
+ fileEditorManager.openFile(file, false);
+ }
+ }
+ }
+ }
+ }
+
+ public void update(AnActionEvent e) {
+ super.update(e);
+ if (ThumbnailViewActionUtil.setEnabled(e)) {
+ Presentation presentation = e.getPresentation();
+ ThumbnailView view = ThumbnailViewActionUtil.getVisibleThumbnailView(e);
+ VirtualFile[] selection = view.getSelection();
+ if (selection.length > 0) {
+ if (selection.length == 1 && selection[0].isDirectory()) {
+ presentation.setVisible(true);
+ } else if (selection.length > 0) {
+ boolean notImages = false;
+ ImageFileTypeManager typeManager = ImageFileTypeManager.getInstance();
+ for (VirtualFile file : selection) {
+ notImages |= !typeManager.isImage(file);
+ }
+ presentation.setEnabled(!notImages);
+ presentation.setVisible(false);
+ } else {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ }
+ } else {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ }
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/thumbnail/actions/HideThumbnailsAction.java b/images/src/org/intellij/images/thumbnail/actions/HideThumbnailsAction.java
new file mode 100644
index 000000000000..a401c21123ca
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/actions/HideThumbnailsAction.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.thumbnail.actions;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import org.intellij.images.thumbnail.ThumbnailView;
+import org.intellij.images.thumbnail.actionSystem.ThumbnailViewActionUtil;
+
+/**
+ * Hide tool window.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public final class HideThumbnailsAction extends AnAction {
+ public void actionPerformed(AnActionEvent e) {
+ ThumbnailView view = ThumbnailViewActionUtil.getVisibleThumbnailView(e);
+ if (view != null) {
+ view.setVisible(false);
+ }
+ }
+
+ public void update(AnActionEvent e) {
+ super.update(e);
+ ThumbnailViewActionUtil.setEnabled(e);
+ }
+}
diff --git a/images/src/org/intellij/images/thumbnail/actions/ToggleRecursiveAction.java b/images/src/org/intellij/images/thumbnail/actions/ToggleRecursiveAction.java
new file mode 100644
index 000000000000..afe67a59c63f
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/actions/ToggleRecursiveAction.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.thumbnail.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.ToggleAction;
+import org.intellij.images.thumbnail.ThumbnailView;
+import org.intellij.images.thumbnail.actionSystem.ThumbnailViewActionUtil;
+
+/**
+ * Toggle recursive flag.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public final class ToggleRecursiveAction extends ToggleAction {
+ public boolean isSelected(AnActionEvent e) {
+ ThumbnailView view = ThumbnailViewActionUtil.getVisibleThumbnailView(e);
+ return view != null && view.isRecursive();
+ }
+
+ public void setSelected(AnActionEvent e, boolean state) {
+ ThumbnailView view = ThumbnailViewActionUtil.getVisibleThumbnailView(e);
+ if (view != null) {
+ view.setRecursive(state);
+ }
+ }
+
+ public void update(final AnActionEvent e) {
+ super.update(e);
+ ThumbnailViewActionUtil.setEnabled(e);
+ }
+}
diff --git a/images/src/org/intellij/images/thumbnail/actions/UpFolderAction.java b/images/src/org/intellij/images/thumbnail/actions/UpFolderAction.java
new file mode 100644
index 000000000000..3da9f9505911
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/actions/UpFolderAction.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.thumbnail.actions;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.intellij.images.thumbnail.ThumbnailView;
+import org.intellij.images.thumbnail.actionSystem.ThumbnailViewActionUtil;
+
+/**
+ * Level up to browse images.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public final class UpFolderAction extends AnAction {
+ public void actionPerformed(AnActionEvent e) {
+ ThumbnailView view = ThumbnailViewActionUtil.getVisibleThumbnailView(e);
+ if (view != null) {
+ VirtualFile root = view.getRoot();
+ if (root != null) {
+ VirtualFile parent = root.getParent();
+ if (parent != null) {
+ view.setRoot(parent);
+ }
+ }
+ }
+ }
+
+ public void update(AnActionEvent e) {
+ super.update(e);
+ if (ThumbnailViewActionUtil.setEnabled(e)) {
+ ThumbnailView view = ThumbnailViewActionUtil.getVisibleThumbnailView(e);
+ VirtualFile root = view.getRoot();
+ e.getPresentation().setEnabled(root != null && root.getParent() != null && !view.isRecursive());
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/thumbnail/impl/ThumbnailManagerImpl.java b/images/src/org/intellij/images/thumbnail/impl/ThumbnailManagerImpl.java
new file mode 100644
index 000000000000..529a107870b4
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/impl/ThumbnailManagerImpl.java
@@ -0,0 +1,51 @@
+/*
+ * 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.thumbnail.impl;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.project.Project;
+import org.intellij.images.thumbnail.ThumbnailManager;
+import org.intellij.images.thumbnail.ThumbnailView;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Thumbail manager.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class ThumbnailManagerImpl extends ThumbnailManager implements Disposable {
+ private final Project project;
+ private ThumbnailView thumbnailView;
+
+ public ThumbnailManagerImpl(Project project) {
+ this.project = project;
+ }
+
+ @NotNull
+ public final ThumbnailView getThumbnailView() {
+ if (thumbnailView == null) {
+ thumbnailView = new ThumbnailViewImpl(project);
+ }
+ return thumbnailView;
+ }
+
+ public void dispose() {
+ if (thumbnailView != null) {
+ thumbnailView.dispose();
+ thumbnailView = null;
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/thumbnail/impl/ThumbnailSelectInTarget.java b/images/src/org/intellij/images/thumbnail/impl/ThumbnailSelectInTarget.java
new file mode 100644
index 000000000000..3b4cf9881be8
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/impl/ThumbnailSelectInTarget.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.thumbnail.impl;
+
+import com.intellij.ide.SelectInContext;
+import com.intellij.ide.SelectInTarget;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.project.Project;
+import org.intellij.images.fileTypes.ImageFileTypeManager;
+import org.intellij.images.thumbnail.ThumbnailManager;
+import org.intellij.images.thumbnail.ThumbnailView;
+
+final class ThumbnailSelectInTarget implements SelectInTarget {
+ public ThumbnailSelectInTarget() {
+ }
+
+ public boolean canSelect(SelectInContext context) {
+ VirtualFile virtualFile = context.getVirtualFile();
+ return ImageFileTypeManager.getInstance().isImage(virtualFile) && virtualFile.getParent() != null;
+ }
+
+ public void selectIn(SelectInContext context, final boolean requestFocus) {
+ VirtualFile virtualFile = context.getVirtualFile();
+ VirtualFile parent = virtualFile.getParent();
+ if (parent != null) {
+ final Project project = context.getProject();
+ ThumbnailView thumbnailView = ThumbnailManager.getManager(project).getThumbnailView();
+ thumbnailView.setRoot(parent);
+ thumbnailView.setVisible(true);
+ thumbnailView.setSelected(virtualFile, true);
+ thumbnailView.scrollToSelection();
+ }
+ }
+
+ public String toString() {
+ return getToolWindowId();
+ }
+
+ public String getToolWindowId() {
+ return ThumbnailView.TOOLWINDOW_ID;
+ }
+
+ public String getMinorViewId() {
+ return null;
+ }
+
+ public float getWeight() {
+ return 10;
+ }
+}
diff --git a/images/src/org/intellij/images/thumbnail/impl/ThumbnailViewImpl.java b/images/src/org/intellij/images/thumbnail/impl/ThumbnailViewImpl.java
new file mode 100644
index 000000000000..445c245b12d9
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/impl/ThumbnailViewImpl.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.thumbnail.impl;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowAnchor;
+import com.intellij.openapi.wm.ToolWindowManager;
+import icons.ImagesIcons;
+import org.intellij.images.editor.actionSystem.ImageEditorActions;
+import org.intellij.images.thumbnail.ThumbnailView;
+import org.intellij.images.vfs.IfsUtil;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+ * Thumbnail view.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+final class ThumbnailViewImpl implements ThumbnailView {
+
+ private final Project project;
+ private final ToolWindow toolWindow;
+
+ private boolean recursive = false;
+ private VirtualFile root = null;
+ private final ThumbnailViewUI myThubmnailViewUi;
+
+ public ThumbnailViewImpl(Project project) {
+ this.project = project;
+
+ ToolWindowManager windowManager = ToolWindowManager.getInstance(project);
+ myThubmnailViewUi = new ThumbnailViewUI(this);
+ toolWindow = windowManager.registerToolWindow(TOOLWINDOW_ID, myThubmnailViewUi, ToolWindowAnchor.BOTTOM);
+ toolWindow.setIcon(ImagesIcons.ThumbnailToolWindow);
+ setVisible(false);
+ }
+
+ private ThumbnailViewUI getUI() {
+ return myThubmnailViewUi;
+ }
+
+ public void setRoot(@NotNull VirtualFile root) {
+ this.root = root;
+ updateUI();
+ }
+
+ public VirtualFile getRoot() {
+ return root;
+ }
+
+ public boolean isRecursive() {
+ return recursive;
+ }
+
+ public void setRecursive(boolean recursive) {
+ this.recursive = recursive;
+ updateUI();
+ }
+
+ public void setSelected(@NotNull VirtualFile file, boolean selected) {
+ if (isVisible()) {
+ getUI().setSelected(file, selected);
+ }
+ }
+
+ public boolean isSelected(@NotNull VirtualFile file) {
+ return isVisible() && getUI().isSelected(file);
+ }
+
+ @NotNull
+ public VirtualFile[] getSelection() {
+ if (isVisible()) {
+ return getUI().getSelection();
+ }
+ return VirtualFile.EMPTY_ARRAY;
+ }
+
+ public void scrollToSelection() {
+ if (isVisible()) {
+ if (!toolWindow.isActive()) {
+ toolWindow.activate(new LazyScroller());
+ }
+ else {
+ getUI().scrollToSelection();
+ }
+ }
+ }
+
+ public boolean isVisible() {
+ return toolWindow.isAvailable();
+ }
+
+ public void activate() {
+ if (isVisible() && !toolWindow.isActive()) {
+ toolWindow.activate(null);
+ }
+ }
+
+ public void setVisible(boolean visible) {
+ toolWindow.setAvailable(visible, null);
+ if (visible) {
+ setTitle();
+ getUI().refresh();
+ }
+ else {
+ getUI().dispose();
+ }
+ }
+
+ private void updateUI() {
+ if (isVisible()) {
+ setTitle();
+ getUI().refresh();
+ }
+ }
+
+ private void setTitle() {
+ toolWindow.setTitle(root != null ? IfsUtil.getReferencePath(project, root) : null);
+ }
+
+ @NotNull
+ public Project getProject() {
+ return project;
+ }
+
+ public void setTransparencyChessboardVisible(boolean visible) {
+ if (isVisible()) {
+ getUI().setTransparencyChessboardVisible(visible);
+ }
+ }
+
+ public boolean isTransparencyChessboardVisible() {
+ return isVisible() && getUI().isTransparencyChessboardVisible();
+ }
+
+ public boolean isEnabledForActionPlace(String place) {
+ // Enable if it not for Editor
+ return isVisible() && !ImageEditorActions.ACTION_PLACE.equals(place);
+ }
+
+ public void dispose() {
+ // Dispose UI
+ getUI().dispose();
+ // Unregister ToolWindow
+ ToolWindowManager windowManager = ToolWindowManager.getInstance(project);
+ windowManager.unregisterToolWindow(TOOLWINDOW_ID);
+ }
+
+ private final class LazyScroller implements Runnable {
+ public void run() {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ getUI().scrollToSelection();
+ }
+ });
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/thumbnail/impl/ThumbnailViewUI.java b/images/src/org/intellij/images/thumbnail/impl/ThumbnailViewUI.java
new file mode 100644
index 000000000000..b30083e88c6f
--- /dev/null
+++ b/images/src/org/intellij/images/thumbnail/impl/ThumbnailViewUI.java
@@ -0,0 +1,586 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.thumbnail.impl;
+
+import com.intellij.ide.CopyPasteSupport;
+import com.intellij.ide.DeleteProvider;
+import com.intellij.ide.PsiActionSupportFactory;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.vfs.*;
+import com.intellij.pom.Navigatable;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.util.PsiUtilBase;
+import com.intellij.ui.IdeBorderFactory;
+import com.intellij.ui.ScrollPaneFactory;
+import com.intellij.ui.SideBorder;
+import com.intellij.ui.components.JBList;
+import org.intellij.images.fileTypes.ImageFileTypeManager;
+import org.intellij.images.options.*;
+import org.intellij.images.thumbnail.ThumbnailView;
+import org.intellij.images.thumbnail.actionSystem.ThumbnailViewActions;
+import org.intellij.images.ui.ImageComponent;
+import org.intellij.images.ui.ImageComponentDecorator;
+import org.intellij.images.ui.ThumbnailComponent;
+import org.intellij.images.ui.ThumbnailComponentUI;
+import org.intellij.images.vfs.IfsUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Set;
+
+final class ThumbnailViewUI extends JPanel implements DataProvider, Disposable {
+ private final VirtualFileListener vfsListener = new VFSListener();
+ private final OptionsChangeListener optionsListener = new OptionsChangeListener();
+
+ private static final Navigatable[] EMPTY_NAVIGATABLE_ARRAY = new Navigatable[]{};
+
+ private final ThumbnailView thumbnailView;
+ private final CopyPasteSupport copyPasteSupport;
+ private final DeleteProvider deleteProvider;
+ private ThumbnailListCellRenderer cellRenderer;
+ private JList list;
+ private static final Comparator<VirtualFile> VIRTUAL_FILE_COMPARATOR = new Comparator<VirtualFile>() {
+ public int compare(VirtualFile o1, VirtualFile o2) {
+ if (o1.isDirectory() && !o2.isDirectory()) {
+ return -1;
+ }
+ if (o2.isDirectory() && !o1.isDirectory()) {
+ return 1;
+ }
+
+ return o1.getPath().toLowerCase().compareTo(o2.getPath().toLowerCase());
+ }
+ };
+
+ public ThumbnailViewUI(ThumbnailViewImpl thumbnailView) {
+ super(new BorderLayout());
+
+ this.thumbnailView = thumbnailView;
+
+ final PsiActionSupportFactory factory = PsiActionSupportFactory.getInstance();
+ copyPasteSupport = factory.createPsiBasedCopyPasteSupport(thumbnailView.getProject(), this, new PsiActionSupportFactory.PsiElementSelector() {
+ public PsiElement[] getSelectedElements() {
+ return (PsiElement[]) getData(LangDataKeys.PSI_ELEMENT_ARRAY.getName());
+ }
+ });
+
+ deleteProvider = factory.createPsiBasedDeleteProvider();
+
+ }
+
+ private void createUI() {
+ if (cellRenderer == null || list == null) {
+ cellRenderer = new ThumbnailListCellRenderer();
+ ImageComponent imageComponent = cellRenderer.getImageComponent();
+
+ VirtualFileManager.getInstance().addVirtualFileListener(vfsListener);
+
+ Options options = OptionsManager.getInstance().getOptions();
+ EditorOptions editorOptions = options.getEditorOptions();
+ // Set options
+ TransparencyChessboardOptions chessboardOptions = editorOptions.getTransparencyChessboardOptions();
+ imageComponent.setTransparencyChessboardVisible(chessboardOptions.isShowDefault());
+ imageComponent.setTransparencyChessboardCellSize(chessboardOptions.getCellSize());
+ imageComponent.setTransparencyChessboardWhiteColor(chessboardOptions.getWhiteColor());
+ imageComponent.setTransparencyChessboardBlankColor(chessboardOptions.getBlackColor());
+
+ options.addPropertyChangeListener(optionsListener);
+
+ list = new JBList();
+ list.setModel(new DefaultListModel());
+ list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
+ list.setVisibleRowCount(-1);
+ list.setCellRenderer(cellRenderer);
+ list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+
+ ThumbnailsMouseAdapter mouseListener = new ThumbnailsMouseAdapter();
+ list.addMouseListener(mouseListener);
+ list.addMouseMotionListener(mouseListener);
+
+ ThumbnailComponentUI componentUI = (ThumbnailComponentUI) UIManager.getUI(cellRenderer);
+ Dimension preferredSize = componentUI.getPreferredSize(cellRenderer);
+
+ list.setFixedCellWidth(preferredSize.width);
+ list.setFixedCellHeight(preferredSize.height);
+
+
+ JScrollPane scrollPane =
+ ScrollPaneFactory.createScrollPane(list, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+ ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ scrollPane.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP));
+
+ ActionManager actionManager = ActionManager.getInstance();
+ ActionGroup actionGroup = (ActionGroup) actionManager.getAction(ThumbnailViewActions.GROUP_TOOLBAR);
+ ActionToolbar actionToolbar = actionManager.createActionToolbar(
+ ThumbnailViewActions.ACTION_PLACE, actionGroup, true
+ );
+ actionToolbar.setTargetComponent(this);
+
+ JComponent toolbar = actionToolbar.getComponent();
+
+ FocusRequester focusRequester = new FocusRequester();
+ toolbar.addMouseListener(focusRequester);
+ scrollPane.addMouseListener(focusRequester);
+
+ add(toolbar, BorderLayout.NORTH);
+ add(scrollPane, BorderLayout.CENTER);
+ }
+ }
+
+ public void refresh() {
+ createUI();
+ if (list != null) {
+ DefaultListModel model = (DefaultListModel) list.getModel();
+ model.clear();
+ VirtualFile root = thumbnailView.getRoot();
+ if (root != null && root.isValid() && root.isDirectory()) {
+ Set<VirtualFile> files = findFiles(root.getChildren());
+ VirtualFile[] virtualFiles = VfsUtil.toVirtualFileArray(files);
+ Arrays.sort(virtualFiles, VIRTUAL_FILE_COMPARATOR);
+
+ model.ensureCapacity(model.size() + virtualFiles.length + 1);
+ for (VirtualFile virtualFile : virtualFiles) {
+ model.addElement(virtualFile);
+ }
+ if (model.size() > 0) {
+ list.setSelectedIndex(0);
+ }
+ } else {
+ thumbnailView.setVisible(false);
+ }
+ }
+ }
+
+ public boolean isTransparencyChessboardVisible() {
+ createUI();
+ return cellRenderer.getImageComponent().isTransparencyChessboardVisible();
+ }
+
+ public void setTransparencyChessboardVisible(boolean visible) {
+ createUI();
+ cellRenderer.getImageComponent().setTransparencyChessboardVisible(visible);
+ list.repaint();
+ }
+
+ public void setSelected(VirtualFile file, boolean selected) {
+ createUI();
+ list.setSelectedValue(file, false);
+ }
+
+ public void scrollToSelection() {
+ int minSelectionIndex = list.getMinSelectionIndex();
+ int maxSelectionIndex = list.getMaxSelectionIndex();
+ if (minSelectionIndex != -1 && maxSelectionIndex != -1) {
+ list.scrollRectToVisible(list.getCellBounds(minSelectionIndex, maxSelectionIndex));
+ }
+ }
+
+ public boolean isSelected(VirtualFile file) {
+ int index = ((DefaultListModel) list.getModel()).indexOf(file);
+ return index != -1 && list.isSelectedIndex(index);
+ }
+
+ @NotNull
+ public VirtualFile[] getSelection() {
+ if (list != null) {
+ Object[] selectedValues = list.getSelectedValues();
+ if (selectedValues != null) {
+ VirtualFile[] files = new VirtualFile[selectedValues.length];
+ for (int i = 0; i < selectedValues.length; i++) {
+ files[i] = (VirtualFile) selectedValues[i];
+ }
+ return files;
+ }
+ }
+ return VirtualFile.EMPTY_ARRAY;
+ }
+
+ private final class ThumbnailListCellRenderer extends ThumbnailComponent
+ implements ListCellRenderer {
+ private final ImageFileTypeManager typeManager = ImageFileTypeManager.getInstance();
+
+ public Component getListCellRendererComponent(
+ JList list, Object value, int index, boolean isSelected, boolean cellHasFocus
+ ) {
+ if (value instanceof VirtualFile) {
+ VirtualFile file = (VirtualFile) value;
+ setFileName(file.getName());
+ setToolTipText(IfsUtil.getReferencePath(thumbnailView.getProject(), file));
+ setDirectory(file.isDirectory());
+ if (file.isDirectory()) {
+ int imagesCount = 0;
+ VirtualFile[] children = file.getChildren();
+ for (VirtualFile child : children) {
+ if (typeManager.isImage(child)) {
+ imagesCount++;
+ if (imagesCount > 100) {
+ break;
+ }
+ }
+ }
+ setImagesCount(imagesCount);
+ } else {
+ // File rendering
+ setFileSize(file.getLength());
+ try {
+ BufferedImage image = IfsUtil.getImage(file);
+ ImageComponent imageComponent = getImageComponent();
+ imageComponent.getDocument().setValue(image);
+ setFormat(IfsUtil.getFormat(file));
+ } catch (Exception e) {
+ // Ignore
+ ImageComponent imageComponent = getImageComponent();
+ imageComponent.getDocument().setValue(null);
+ }
+ }
+
+ } else {
+ ImageComponent imageComponent = getImageComponent();
+ imageComponent.getDocument().setValue(null);
+ setFileName(null);
+ setFileSize(0);
+ setToolTipText(null);
+ }
+
+ if (isSelected) {
+ setForeground(list.getSelectionForeground());
+ setBackground(list.getSelectionBackground());
+ } else {
+ setForeground(list.getForeground());
+ setBackground(list.getBackground());
+ }
+
+ return this;
+ }
+
+ }
+
+ private Set<VirtualFile> findFiles(VirtualFile[] roots) {
+ Set<VirtualFile> files = new HashSet<VirtualFile>();
+ for (VirtualFile root : roots) {
+ files.addAll(findFiles(root));
+ }
+ return files;
+ }
+
+ private Set<VirtualFile> findFiles(VirtualFile file) {
+ Set<VirtualFile> files = new HashSet<VirtualFile>(0);
+ Project project = thumbnailView.getProject();
+ if (!project.isDisposed()) {
+ ProjectRootManager rootManager = ProjectRootManager.getInstance(project);
+ boolean projectIgnored = rootManager.getFileIndex().isIgnored(file);
+
+ if (!projectIgnored && !FileTypeManager.getInstance().isFileIgnored(file)) {
+ ImageFileTypeManager typeManager = ImageFileTypeManager.getInstance();
+ if (file.isDirectory()) {
+ if (thumbnailView.isRecursive()) {
+ files.addAll(findFiles(file.getChildren()));
+ } else if (isImagesInDirectory(file)) {
+ files.add(file);
+ }
+ } else if (typeManager.isImage(file)) {
+ files.add(file);
+ }
+ }
+ }
+ return files;
+ }
+
+ private boolean isImagesInDirectory(VirtualFile dir) {
+ ImageFileTypeManager typeManager = ImageFileTypeManager.getInstance();
+ VirtualFile[] files = dir.getChildren();
+ for (VirtualFile file : files) {
+ if (file.isDirectory()) {
+ // We can be sure for fast searching
+ return true;
+ }
+ if (typeManager.isImage(file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private final class ThumbnailsMouseAdapter extends MouseAdapter implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ Point point = e.getPoint();
+ int index = list.locationToIndex(point);
+ if (index != -1) {
+ Rectangle cellBounds = list.getCellBounds(index, index);
+ if (!cellBounds.contains(point) &&
+ (KeyEvent.CTRL_DOWN_MASK & e.getModifiersEx()) != KeyEvent.CTRL_DOWN_MASK) {
+ list.clearSelection();
+ e.consume();
+ }
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ }
+
+
+ public void mousePressed(MouseEvent e) {
+ Point point = e.getPoint();
+ int index = list.locationToIndex(point);
+ if (index != -1) {
+ Rectangle cellBounds = list.getCellBounds(index, index);
+ if (!cellBounds.contains(point) && (KeyEvent.CTRL_DOWN_MASK & e.getModifiersEx()) != KeyEvent.CTRL_DOWN_MASK) {
+ list.clearSelection();
+ e.consume();
+ }
+ }
+ }
+
+ public void mouseClicked(MouseEvent e) {
+ Point point = e.getPoint();
+ int index = list.locationToIndex(point);
+ if (index != -1) {
+ Rectangle cellBounds = list.getCellBounds(index, index);
+ if (!cellBounds.contains(point) && (KeyEvent.CTRL_DOWN_MASK & e.getModifiersEx()) != KeyEvent.CTRL_DOWN_MASK) {
+ index = -1;
+ list.clearSelection();
+ }
+ }
+ if (index != -1) {
+ if (MouseEvent.BUTTON1 == e.getButton() && e.getClickCount() == 2) {
+ // Double click
+ list.setSelectedIndex(index);
+ VirtualFile selected = (VirtualFile) list.getSelectedValue();
+ if (selected != null) {
+ if (selected.isDirectory()) {
+ thumbnailView.setRoot(selected);
+ } else {
+ FileEditorManager fileEditorManager = FileEditorManager.getInstance(thumbnailView.getProject());
+ fileEditorManager.openFile(selected, true);
+ }
+ e.consume();
+ }
+ }
+ if (MouseEvent.BUTTON3 == e.getButton() && e.getClickCount() == 1) {
+ // Ensure that we have selection
+ if ((KeyEvent.CTRL_DOWN_MASK & e.getModifiersEx()) != KeyEvent.CTRL_DOWN_MASK) {
+ // Ctrl is not pressed
+ list.setSelectedIndex(index);
+ } else {
+ // Ctrl is pressed
+ list.getSelectionModel().addSelectionInterval(index, index);
+ }
+ // Single right click
+ ActionManager actionManager = ActionManager.getInstance();
+ ActionGroup actionGroup = (ActionGroup) actionManager.getAction(ThumbnailViewActions.GROUP_POPUP);
+ ActionPopupMenu menu = actionManager.createActionPopupMenu(ThumbnailViewActions.ACTION_PLACE, actionGroup);
+ JPopupMenu popupMenu = menu.getComponent();
+ popupMenu.pack();
+ popupMenu.show(e.getComponent(), e.getX(), e.getY());
+
+ e.consume();
+ }
+ }
+ }
+ }
+
+ @Nullable
+ public Object getData(String dataId) {
+ if (PlatformDataKeys.PROJECT.is(dataId)) {
+ return thumbnailView.getProject();
+ } else if (PlatformDataKeys.VIRTUAL_FILE.is(dataId)) {
+ VirtualFile[] selectedFiles = getSelectedFiles();
+ return selectedFiles.length > 0 ? selectedFiles[0] : null;
+ } else if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
+ return getSelectedFiles();
+ } else if (LangDataKeys.PSI_FILE.is(dataId)) {
+ return getData(LangDataKeys.PSI_ELEMENT.getName());
+ } else if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
+ VirtualFile[] selectedFiles = getSelectedFiles();
+ return selectedFiles.length > 0 ? PsiManager.getInstance(thumbnailView.getProject()).findFile(selectedFiles[0]) : null;
+ } else if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
+ return getSelectedElements();
+ } else if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
+ VirtualFile[] selectedFiles = getSelectedFiles();
+ return new ThumbnailNavigatable(selectedFiles.length > 0 ? selectedFiles[0] : null);
+ } else if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
+ return copyPasteSupport.getCopyProvider();
+ } else if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
+ return copyPasteSupport.getCutProvider();
+ } else if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
+ return copyPasteSupport.getPasteProvider();
+ } else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
+ return deleteProvider;
+ } else if (PlatformDataKeys.NAVIGATABLE_ARRAY.is(dataId)) {
+ VirtualFile[] selectedFiles = getSelectedFiles();
+ Set<Navigatable> navigatables = new HashSet<Navigatable>(selectedFiles.length);
+ for (VirtualFile selectedFile : selectedFiles) {
+ if (!selectedFile.isDirectory()) {
+ navigatables.add(new ThumbnailNavigatable(selectedFile));
+ }
+ }
+ return navigatables.toArray(EMPTY_NAVIGATABLE_ARRAY);
+ } else if (ThumbnailView.DATA_KEY.is(dataId)) {
+ return thumbnailView;
+ } else if (ImageComponentDecorator.DATA_KEY.is(dataId)) {
+ return thumbnailView;
+ }
+
+ return null;
+ }
+
+
+ @NotNull
+ private PsiElement[] getSelectedElements() {
+ VirtualFile[] selectedFiles = getSelectedFiles();
+ Set<PsiElement> psiElements = new HashSet<PsiElement>(selectedFiles.length);
+ PsiManager psiManager = PsiManager.getInstance(thumbnailView.getProject());
+ for (VirtualFile file : selectedFiles) {
+ PsiFile psiFile = psiManager.findFile(file);
+ PsiElement element = psiFile != null ? psiFile : psiManager.findDirectory(file);
+ if (element != null) {
+ psiElements.add(element);
+ }
+ }
+ return PsiUtilBase.toPsiElementArray(psiElements);
+ }
+
+ @NotNull
+ private VirtualFile[] getSelectedFiles() {
+ if (list != null) {
+ Object[] selectedValues = list.getSelectedValues();
+ if (selectedValues != null) {
+ VirtualFile[] files = new VirtualFile[selectedValues.length];
+ for (int i = 0; i < selectedValues.length; i++) {
+ files[i] = (VirtualFile) selectedValues[i];
+ }
+ return files;
+ }
+ }
+ return VirtualFile.EMPTY_ARRAY;
+ }
+
+ public void dispose() {
+ removeAll();
+
+ Options options = OptionsManager.getInstance().getOptions();
+ options.removePropertyChangeListener(optionsListener);
+
+ VirtualFileManager.getInstance().removeVirtualFileListener(vfsListener);
+
+ list = null;
+ cellRenderer = null;
+ }
+
+ private final class ThumbnailNavigatable implements Navigatable {
+ private final VirtualFile file;
+
+ public ThumbnailNavigatable(VirtualFile file) {
+ this.file = file;
+ }
+
+ public void navigate(boolean requestFocus) {
+ if (file != null) {
+ FileEditorManager manager = FileEditorManager.getInstance(thumbnailView.getProject());
+ manager.openFile(file, true);
+ }
+ }
+
+ public boolean canNavigate() {
+ return file != null;
+ }
+
+ public boolean canNavigateToSource() {
+ return file != null;
+ }
+ }
+
+ private final class VFSListener extends VirtualFileAdapter {
+ public void contentsChanged(VirtualFileEvent event) {
+ VirtualFile file = event.getFile();
+ if (list != null) {
+ int index = ((DefaultListModel) list.getModel()).indexOf(file);
+ if (index != -1) {
+ Rectangle cellBounds = list.getCellBounds(index, index);
+ list.repaint(cellBounds);
+ }
+ }
+ }
+
+ public void fileDeleted(VirtualFileEvent event) {
+ VirtualFile file = event.getFile();
+ VirtualFile root = thumbnailView.getRoot();
+ if (root != null && VfsUtil.isAncestor(file, root, false)) {
+ refresh();
+ }
+ if (list != null) {
+ ((DefaultListModel) list.getModel()).removeElement(file);
+ }
+ }
+
+ public void propertyChanged(VirtualFilePropertyEvent event) {
+ refresh();
+ }
+
+ public void fileCreated(VirtualFileEvent event) {
+ refresh();
+ }
+
+ public void fileMoved(VirtualFileMoveEvent event) {
+ refresh();
+ }
+ }
+
+ private final 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 imageComponent = cellRenderer.getImageComponent();
+ imageComponent.setTransparencyChessboardCellSize(chessboardOptions.getCellSize());
+ imageComponent.setTransparencyChessboardWhiteColor(chessboardOptions.getWhiteColor());
+ imageComponent.setTransparencyChessboardBlankColor(chessboardOptions.getBlackColor());
+ imageComponent.setGridLineZoomFactor(gridOptions.getLineZoomFactor());
+ imageComponent.setGridLineSpan(gridOptions.getLineSpan());
+ imageComponent.setGridLineColor(gridOptions.getLineColor());
+ }
+ }
+
+ private class FocusRequester extends MouseAdapter {
+ public void mouseClicked(MouseEvent e) {
+ requestFocus();
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/ui/ImageComponent.java b/images/src/org/intellij/images/ui/ImageComponent.java
new file mode 100644
index 000000000000..7394be3721d9
--- /dev/null
+++ b/images/src/org/intellij/images/ui/ImageComponent.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.ui;
+
+import org.intellij.images.editor.ImageDocument;
+import org.intellij.images.options.GridOptions;
+import org.intellij.images.options.TransparencyChessboardOptions;
+import org.jetbrains.annotations.NonNls;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Image component is draw image box with effects.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public class ImageComponent extends JComponent {
+ @NonNls
+ private static final String TRANSPARENCY_CHESSBOARD_CELL_SIZE_PROP = "TransparencyChessboard.cellSize";
+ @NonNls
+ private static final String TRANSPARENCY_CHESSBOARD_WHITE_COLOR_PROP = "TransparencyChessboard.whiteColor";
+ @NonNls
+ private static final String TRANSPARENCY_CHESSBOARD_BLACK_COLOR_PROP = "TransparencyChessboard.blackColor";
+ @NonNls
+ private static final String TRANSPARENCY_CHESSBOARD_VISIBLE_PROP = "TransparencyChessboard.visible";
+ @NonNls
+ private static final String GRID_LINE_ZOOM_FACTOR_PROP = "Grid.lineZoomFactor";
+ @NonNls
+ private static final String GRID_LINE_SPAN_PROP = "Grid.lineSpan";
+ @NonNls
+ private static final String GRID_LINE_COLOR_PROP = "Grid.lineColor";
+ @NonNls
+ private static final String GRID_VISIBLE_PROP = "Grid.visible";
+
+ /**
+ * @see #getUIClassID
+ * @see #readObject
+ */
+ @NonNls
+ private static final String uiClassID = "ImageComponentUI";
+
+ static {
+ UIManager.getDefaults().put(uiClassID, ImageComponentUI.class.getName());
+ }
+
+ private final ImageDocument document = new ImageDocumentImpl();
+ private final Grid grid = new Grid();
+ private final Chessboard chessboard = new Chessboard();
+
+ public ImageComponent() {
+ updateUI();
+ }
+
+ public ImageDocument getDocument() {
+ return document;
+ }
+
+ public void setTransparencyChessboardCellSize(int cellSize) {
+ int oldValue = chessboard.getCellSize();
+ if (oldValue != cellSize) {
+ chessboard.setCellSize(cellSize);
+ firePropertyChange(TRANSPARENCY_CHESSBOARD_CELL_SIZE_PROP, oldValue, cellSize);
+ }
+ }
+
+ public void setTransparencyChessboardWhiteColor(Color color) {
+ Color oldValue = chessboard.getWhiteColor();
+ if (oldValue != null && !oldValue.equals(color) || oldValue == null && color != null) {
+ chessboard.setWhiteColor(color);
+ firePropertyChange(TRANSPARENCY_CHESSBOARD_WHITE_COLOR_PROP, oldValue, color);
+ }
+ }
+
+ public void setTransparencyChessboardBlankColor(Color color) {
+ Color oldValue = chessboard.getBlackColor();
+ if (oldValue != null && !oldValue.equals(color) || oldValue == null && color != null) {
+ chessboard.setBlackColor(color);
+ firePropertyChange(TRANSPARENCY_CHESSBOARD_BLACK_COLOR_PROP, oldValue, color);
+ }
+ }
+
+ public void setTransparencyChessboardVisible(boolean visible) {
+ boolean oldValue = chessboard.isVisible();
+ if (oldValue != visible) {
+ chessboard.setVisible(visible);
+ firePropertyChange(TRANSPARENCY_CHESSBOARD_VISIBLE_PROP, oldValue, visible);
+ }
+ }
+
+ public int getTransparencyChessboardCellSize() {
+ return chessboard.getCellSize();
+ }
+
+ public Color getTransparencyChessboardWhiteColor() {
+ return chessboard.getWhiteColor();
+ }
+
+ public Color getTransparencyChessboardBlackColor() {
+ return chessboard.getBlackColor();
+ }
+
+ public boolean isTransparencyChessboardVisible() {
+ return chessboard.isVisible();
+ }
+
+ public void setGridLineZoomFactor(int lineZoomFactor) {
+ int oldValue = grid.getLineZoomFactor();
+ if (oldValue != lineZoomFactor) {
+ grid.setLineZoomFactor(lineZoomFactor);
+ firePropertyChange(GRID_LINE_ZOOM_FACTOR_PROP, oldValue, lineZoomFactor);
+ }
+ }
+
+ public void setGridLineSpan(int lineSpan) {
+ int oldValue = grid.getLineSpan();
+ if (oldValue != lineSpan) {
+ grid.setLineSpan(lineSpan);
+ firePropertyChange(GRID_LINE_SPAN_PROP, oldValue, lineSpan);
+ }
+ }
+
+ public void setGridLineColor(Color color) {
+ Color oldValue = grid.getLineColor();
+ if (oldValue != null && !oldValue.equals(color) || oldValue == null && color != null) {
+ grid.setLineColor(color);
+ firePropertyChange(GRID_LINE_COLOR_PROP, oldValue, color);
+ }
+ }
+
+ public void setGridVisible(boolean visible) {
+ boolean oldValue = grid.isVisible();
+ if (oldValue != visible) {
+ grid.setVisible(visible);
+ firePropertyChange(GRID_VISIBLE_PROP, oldValue, visible);
+ }
+ }
+
+ public int getGridLineZoomFactor() {
+ return grid.getLineZoomFactor();
+ }
+
+ public int getGridLineSpan() {
+ return grid.getLineSpan();
+ }
+
+ public Color getGridLineColor() {
+ return grid.getLineColor();
+ }
+
+ public boolean isGridVisible() {
+ return grid.isVisible();
+ }
+
+ public void setCanvasSize(int width, int height) {
+ setSize(width + 4, height + 4);
+ }
+
+ public void setCanvasSize(Dimension dimension) {
+ setCanvasSize(dimension.width, dimension.height);
+ }
+
+ public Dimension getCanvasSize() {
+ Dimension size = getSize();
+ return new Dimension(size.width - 4, size.height - 4);
+ }
+
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ public void updateUI() {
+ setUI(UIManager.getUI(this));
+ }
+
+ private static final class ImageDocumentImpl implements ImageDocument {
+ private final Set<ChangeListener> listeners = new HashSet<ChangeListener>(0);
+ private BufferedImage image;
+ private String format;
+ private Image renderer;
+
+ public Image getRenderer() {
+ return renderer;
+ }
+
+ public BufferedImage getValue() {
+ return image;
+ }
+
+ public void setValue(BufferedImage image) {
+ this.image = image;
+ this.renderer = image != null ? Toolkit.getDefaultToolkit().createImage(image.getSource()) : null;
+ fireChangeEvent(new ChangeEvent(this));
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+
+ public void setFormat(String format) {
+ this.format = format;
+ fireChangeEvent(new ChangeEvent(this));
+ }
+
+ private void fireChangeEvent(ChangeEvent e) {
+ for (ChangeListener listener : listeners) {
+ listener.stateChanged(e);
+ }
+ }
+
+ public void addChangeListener(ChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeChangeListener(ChangeListener listener) {
+ listeners.remove(listener);
+ }
+ }
+
+ private static final class Chessboard {
+ private int cellSize = TransparencyChessboardOptions.DEFAULT_CELL_SIZE;
+ private Color whiteColor = TransparencyChessboardOptions.DEFAULT_WHITE_COLOR;
+ private Color blackColor = TransparencyChessboardOptions.DEFAULT_BLACK_COLOR;
+ private boolean visible = false;
+
+ public int getCellSize() {
+ return cellSize;
+ }
+
+ public void setCellSize(int cellSize) {
+ this.cellSize = cellSize;
+ }
+
+ public Color getWhiteColor() {
+ return whiteColor;
+ }
+
+ public void setWhiteColor(Color whiteColor) {
+ this.whiteColor = whiteColor;
+ }
+
+ public Color getBlackColor() {
+ return blackColor;
+ }
+
+ public void setBlackColor(Color blackColor) {
+ this.blackColor = blackColor;
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ }
+ }
+
+ private static final class Grid {
+ private int lineZoomFactor = GridOptions.DEFAULT_LINE_ZOOM_FACTOR;
+ private int lineSpan = GridOptions.DEFAULT_LINE_SPAN;
+ private Color lineColor = GridOptions.DEFAULT_LINE_COLOR;
+ private boolean visible = false;
+
+ public int getLineZoomFactor() {
+ return lineZoomFactor;
+ }
+
+ public void setLineZoomFactor(int lineZoomFactor) {
+ this.lineZoomFactor = lineZoomFactor;
+ }
+
+ public int getLineSpan() {
+ return lineSpan;
+ }
+
+ public void setLineSpan(int lineSpan) {
+ this.lineSpan = lineSpan;
+ }
+
+ public Color getLineColor() {
+ return lineColor;
+ }
+
+ public void setLineColor(Color lineColor) {
+ this.lineColor = lineColor;
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/ui/ImageComponentDecorator.java b/images/src/org/intellij/images/ui/ImageComponentDecorator.java
new file mode 100644
index 000000000000..da9ef9215b3a
--- /dev/null
+++ b/images/src/org/intellij/images/ui/ImageComponentDecorator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.ui;
+
+import com.intellij.openapi.actionSystem.DataKey;
+
+/**
+ * Image Component manager. It can toggle backround transparency, grid, etc.
+ *
+ * @author Alexey Efimov
+ */
+public interface ImageComponentDecorator {
+ DataKey<ImageComponentDecorator> DATA_KEY = DataKey.create(ImageComponentDecorator.class.getName());
+
+ void setTransparencyChessboardVisible(boolean visible);
+
+ boolean isTransparencyChessboardVisible();
+
+ /**
+ * Return <code>true</code> if this decorator is enabled for this action place.
+ *
+ * @param place Action place
+ * @return <code>true</code> is decorator is enabled
+ */
+ boolean isEnabledForActionPlace(String place);
+}
diff --git a/images/src/org/intellij/images/ui/ImageComponentUI.java b/images/src/org/intellij/images/ui/ImageComponentUI.java
new file mode 100644
index 000000000000..0cedcf98e143
--- /dev/null
+++ b/images/src/org/intellij/images/ui/ImageComponentUI.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.ui;
+
+import com.intellij.util.ui.UIUtil;
+import org.intellij.images.editor.ImageDocument;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+/**
+ * UI for {@link ImageComponent}.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public class ImageComponentUI extends ComponentUI {
+ private static final ImageComponentUI ui = new ImageComponentUI();
+
+ public void paint(Graphics g, JComponent c) {
+ ImageComponent ic = (ImageComponent)c;
+ if (ic != null) {
+ ImageDocument document = ic.getDocument();
+ BufferedImage image = document.getValue();
+ if (image != null) {
+ paintBorder(g, ic);
+
+ Dimension size = ic.getCanvasSize();
+ Graphics igc = g.create(2, 2, size.width, size.height);
+
+ // Transparency chessboard
+ if (ic.isTransparencyChessboardVisible()) {
+ paintChessboard(igc, ic);
+ }
+
+ paintImage(igc, ic);
+
+ // Grid
+ if (ic.isGridVisible()) {
+ paintGrid(igc, ic);
+ }
+
+ igc.dispose();
+ }
+ }
+ }
+
+ private void paintBorder(Graphics g, ImageComponent ic) {
+ Dimension size = ic.getSize();
+ g.setColor(ic.getTransparencyChessboardBlackColor());
+ g.drawRect(0, 0, size.width - 1, size.height - 1);
+ }
+
+ private void paintChessboard(Graphics g, ImageComponent ic) {
+ Dimension size = ic.getCanvasSize();
+ // Create pattern
+ int cellSize = ic.getTransparencyChessboardCellSize();
+ int patternSize = 2 * cellSize;
+ BufferedImage pattern = UIUtil.createImage(patternSize, patternSize, BufferedImage.TYPE_INT_ARGB);
+ Graphics imageGraphics = pattern.getGraphics();
+ imageGraphics.setColor(ic.getTransparencyChessboardWhiteColor());
+ imageGraphics.fillRect(0, 0, patternSize, patternSize);
+ imageGraphics.setColor(ic.getTransparencyChessboardBlackColor());
+ imageGraphics.fillRect(0, cellSize, cellSize, cellSize);
+ imageGraphics.fillRect(cellSize, 0, cellSize, cellSize);
+
+ ((Graphics2D)g).setPaint(new TexturePaint(pattern, new Rectangle(0, 0, patternSize, patternSize)));
+ g.fillRect(0, 0, size.width, size.height);
+ }
+
+ private void paintImage(Graphics g, ImageComponent ic) {
+ ImageDocument document = ic.getDocument();
+ Dimension size = ic.getCanvasSize();
+ g.drawImage(document.getRenderer(), 0, 0, size.width, size.height, ic);
+ }
+
+ private void paintGrid(Graphics g, ImageComponent ic) {
+ Dimension size = ic.getCanvasSize();
+ BufferedImage image = ic.getDocument().getValue();
+ int imageWidth = image.getWidth();
+ int imageHeight = image.getHeight();
+ double zoomX = (double)size.width / (double)imageWidth;
+ double zoomY = (double)size.height / (double)imageHeight;
+ double zoomFactor = (zoomX + zoomY) / 2.0d;
+ if (zoomFactor >= ic.getGridLineZoomFactor()) {
+ g.setColor(ic.getGridLineColor());
+ int ls = ic.getGridLineSpan();
+ for (int dx = ls; dx < imageWidth; dx += ls) {
+ UIUtil.drawLine(g, (int)((double)dx * zoomX), 0, (int)((double)dx * zoomX), size.height);
+ }
+ for (int dy = ls; dy < imageHeight; dy += ls) {
+ UIUtil.drawLine(g, 0, (int)((double)dy * zoomY), size.width, (int)((double)dy * zoomY));
+ }
+ }
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static ComponentUI createUI(JComponent c) {
+ return ui;
+ }
+}
diff --git a/images/src/org/intellij/images/ui/ThumbnailComponent.java b/images/src/org/intellij/images/ui/ThumbnailComponent.java
new file mode 100644
index 000000000000..953bae78b7d7
--- /dev/null
+++ b/images/src/org/intellij/images/ui/ThumbnailComponent.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.ui;
+
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NonNls;
+
+import javax.swing.*;
+
+/**
+ * Thumbnail component.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public class ThumbnailComponent extends JComponent {
+ @NonNls
+ private static final String FORMAT_PROP = "format";
+ @NonNls
+ private static final String FILE_SIZE_PROP = "fileSize";
+ @NonNls
+ private static final String FILE_NAME_PROP = "fileName";
+ @NonNls
+ private static final String DIRECTORY_PROP = "directory";
+ @NonNls
+ private static final String IMAGES_COUNT_PROP = "imagesCount";
+
+ /**
+ * @see #getUIClassID
+ * @see #readObject
+ */
+ @NonNls
+ private static final String uiClassID = "ThumbnailComponentUI";
+
+ static {
+ UIManager.getDefaults().put(uiClassID, ThumbnailComponentUI.class.getName());
+ }
+
+ /**
+ * Image component for rendering thumbnail image.
+ */
+ private final ImageComponent imageComponent = new ImageComponent();
+
+ private String format;
+ private long fileSize;
+ private String fileName;
+ private boolean directory;
+ private int imagesCount;
+
+ public ThumbnailComponent() {
+ updateUI();
+ }
+
+ public ImageComponent getImageComponent() {
+ return imageComponent;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public void setFormat(String format) {
+ String oldValue = this.format;
+ if (oldValue != null && !oldValue.equals(format) || oldValue == null && format != null) {
+ this.format = format;
+ firePropertyChange(FORMAT_PROP, oldValue, this.format);
+ }
+ }
+
+ public long getFileSize() {
+ return fileSize;
+ }
+
+ public void setFileSize(long fileSize) {
+ long oldValue = this.fileSize;
+ if (oldValue != fileSize) {
+ this.fileSize = fileSize;
+ firePropertyChange(FILE_SIZE_PROP, new Long(oldValue), new Long(this.fileSize));
+ }
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ String oldValue = this.fileName;
+ if (oldValue != null && !oldValue.equals(fileName) || oldValue == null && fileName != null) {
+ this.fileName = fileName;
+ firePropertyChange(FILE_NAME_PROP, oldValue, this.fileName);
+ }
+ }
+
+ public boolean isDirectory() {
+ return directory;
+ }
+
+ public void setDirectory(boolean directory) {
+ boolean oldValue = this.directory;
+ if (oldValue != directory) {
+ this.directory = directory;
+ firePropertyChange(DIRECTORY_PROP, oldValue, this.directory);
+ }
+ }
+
+ public int getImagesCount() {
+ return imagesCount;
+ }
+
+ public void setImagesCount(int imagesCount) {
+ int oldValue = this.imagesCount;
+ if (oldValue != imagesCount) {
+ this.imagesCount = imagesCount;
+ firePropertyChange(IMAGES_COUNT_PROP, oldValue, this.imagesCount);
+ }
+ }
+
+ public String getFileSizeText() {
+ return StringUtil.formatFileSize(fileSize);
+ }
+
+ public void updateUI() {
+ setUI(UIManager.getUI(this));
+ }
+
+ public String getUIClassID() {
+ return uiClassID;
+ }
+} \ No newline at end of file
diff --git a/images/src/org/intellij/images/ui/ThumbnailComponentUI.java b/images/src/org/intellij/images/ui/ThumbnailComponentUI.java
new file mode 100644
index 000000000000..ddb4a663b2d5
--- /dev/null
+++ b/images/src/org/intellij/images/ui/ThumbnailComponentUI.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.ui;
+
+import com.intellij.openapi.ui.Messages;
+import com.intellij.ui.JBColor;
+import com.intellij.util.ui.UIUtil;
+import icons.ImagesIcons;
+import org.intellij.images.ImagesBundle;
+import org.intellij.images.editor.ImageDocument;
+import org.jetbrains.annotations.NonNls;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+/**
+ * UI for {@link ThumbnailComponent}.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public class ThumbnailComponentUI extends ComponentUI {
+ @NonNls
+ private static final String DOTS = "...";
+ @NonNls
+ private static final String THUMBNAIL_COMPONENT_ERROR_STRING = "ThumbnailComponent.errorString";
+
+ private static final Color LINE_COLOR = new Color(0x8E, 0xA8, 0xCE);
+ private static final Color PNG_COLOR = new Color(0x80, 0x00, 0x80);
+ private static final Color GIF_COLOR = new Color(0x00, 0x80, 0x00);
+ private static final Color JPG_COLOR = new Color(0x80, 0x80, 0x00);
+ private static final Color BMP_COLOR = new Color(0x00, 0x00, 0x80);
+
+ private static final ThumbnailComponentUI ui = new ThumbnailComponentUI();
+
+ static {
+ UIManager.getDefaults().put(THUMBNAIL_COMPONENT_ERROR_STRING,
+ ImagesBundle.message("thumbnails.component.error.text"));
+ }
+
+
+ public void paint(Graphics g, JComponent c) {
+ ThumbnailComponent tc = (ThumbnailComponent) c;
+ if (tc != null) {
+ paintBackground(g, tc);
+
+ if (tc.isDirectory()) {
+ paintDirectory(g, tc);
+ } else {
+ paintImageThumbnail(g, tc);
+ }
+
+ // File name
+ paintFileName(g, tc);
+ }
+ }
+
+ private void paintDirectory(Graphics g, ThumbnailComponent tc) {
+ // Paint directory icon
+ ImagesIcons.ThumbnailDirectory.paintIcon(tc, g, 5, 5);
+
+ int imagesCount = tc.getImagesCount();
+ if (imagesCount > 0) {
+ final String title = ImagesBundle.message("icons.count", imagesCount);
+
+ Font font = getSmallFont();
+ FontMetrics fontMetrics = g.getFontMetrics(font);
+ g.setColor(Color.BLACK);
+ g.setFont(font);
+ g.drawString(title, 5 + (ImagesIcons.ThumbnailDirectory.getIconWidth() - fontMetrics.stringWidth(title)) / 2, ImagesIcons.ThumbnailDirectory
+ .getIconHeight() / 2 + fontMetrics.getAscent());
+ }
+ }
+
+ private void paintImageThumbnail(Graphics g, ThumbnailComponent tc) {
+ // Paint blank
+ ImagesIcons.ThumbnailBlank.paintIcon(tc, g, 5, 5);
+
+ ImageComponent imageComponent = tc.getImageComponent();
+ ImageDocument document = imageComponent.getDocument();
+ BufferedImage image = document.getValue();
+ if (image != null) {
+ paintImage(g, tc);
+ } else {
+ paintError(g, tc);
+ }
+
+ paintFileSize(g, tc);
+ }
+
+ private void paintBackground(Graphics g, ThumbnailComponent tc) {
+ Dimension size = tc.getSize();
+ g.setColor(tc.getBackground());
+ g.fillRect(0, 0, size.width, size.height);
+ }
+
+ private void paintImage(Graphics g, ThumbnailComponent tc) {
+ ImageComponent imageComponent = tc.getImageComponent();
+ BufferedImage image = imageComponent.getDocument().getValue();
+
+ int blankHeight = ImagesIcons.ThumbnailBlank.getIconHeight();
+
+ // Paint image info (and reduce height of text from available height)
+ blankHeight -= paintImageCaps(g, image);
+ // Paint image format (and reduce height of text from available height)
+ blankHeight -= paintFormatText(tc, g);
+
+ // Paint image
+ paintThumbnail(g, imageComponent, blankHeight);
+ }
+
+ private int paintImageCaps(Graphics g, BufferedImage image) {
+ String description = ImagesBundle.message("icon.dimensions", image.getWidth(), image.getHeight(), image.getColorModel().getPixelSize());
+
+ Font font = getSmallFont();
+ FontMetrics fontMetrics = g.getFontMetrics(font);
+ g.setColor(Color.BLACK);
+ g.setFont(font);
+ g.drawString(description, 8, 7 + fontMetrics.getAscent());
+
+ return fontMetrics.getHeight();
+ }
+
+ private int paintFormatText(ThumbnailComponent tc, Graphics g) {
+ Font font = getSmallFont().deriveFont(Font.BOLD);
+ FontMetrics fontMetrics = g.getFontMetrics(font);
+
+ String format = tc.getFormat().toUpperCase();
+ int stringWidth = fontMetrics.stringWidth(format);
+ int x = ImagesIcons.ThumbnailBlank.getIconWidth() - stringWidth + 2;
+ int y = ImagesIcons.ThumbnailBlank.getIconHeight() - fontMetrics.getHeight() + 4;
+ g.setColor(LINE_COLOR);
+ g.drawLine(x - 3, y - 1, x + stringWidth + 1, y - 1);
+ g.drawLine(x - 4, y, x - 4, y + fontMetrics.getHeight() - 1);
+ g.setColor(getFormatColor(format));
+ g.setFont(font);
+ g.drawString(
+ format,
+ x,
+ y + fontMetrics.getAscent()
+ );
+
+ return fontMetrics.getHeight();
+ }
+
+ private Color getFormatColor(String format) {
+ if ("PNG".equals(format)) {
+ return PNG_COLOR;
+ } else if ("GIF".equals(format)) {
+ return GIF_COLOR;
+ } else if ("JPG".equals(format) || "JPEG".equals(format)) {
+ return JPG_COLOR;
+ } else if ("BMP".equals(format) || "WBMP".equals(format)) {
+ return BMP_COLOR;
+ }
+ return Color.BLACK;
+ }
+
+ private void paintThumbnail(Graphics g, ImageComponent imageComponent, int blankHeight) {
+
+ // Zoom image by available size
+ int maxWidth = ImagesIcons.ThumbnailBlank.getIconWidth() - 10;
+ int maxHeight = blankHeight - 10;
+
+ BufferedImage image = imageComponent.getDocument().getValue();
+ int imageWidth = image.getWidth();
+ int imageHeight = image.getHeight();
+
+ if (imageWidth > maxWidth || imageHeight > maxHeight) {
+ if (imageWidth > maxWidth) {
+ double proportion = (double) maxWidth / (double) imageWidth;
+ imageWidth = maxWidth;
+ imageHeight = (int) ((double) imageHeight * proportion);
+ }
+ if (imageHeight > maxHeight) {
+ double proportion = (double) maxHeight / (double) imageHeight;
+ imageHeight = maxHeight;
+ imageWidth = (int) ((double) imageWidth * proportion);
+ }
+ }
+
+ imageComponent.setCanvasSize(imageWidth, imageHeight);
+ Dimension size = imageComponent.getSize();
+
+ int x = 5 + (ImagesIcons.ThumbnailBlank.getIconWidth() - size.width) / 2;
+ int y = 5 + (ImagesIcons.ThumbnailBlank.getIconHeight() - size.height) / 2;
+
+
+ imageComponent.paint(g.create(x, y, size.width, size.height));
+ }
+
+ private void paintFileName(Graphics g, ThumbnailComponent tc) {
+ Font font = UIUtil.getLabelFont();
+ FontMetrics fontMetrics = g.getFontMetrics(font);
+
+ g.setFont(font);
+ g.setColor(tc.getForeground());
+
+ String fileName = tc.getFileName();
+ String title = fileName;
+ while (fontMetrics.stringWidth(title) > ImagesIcons.ThumbnailBlank.getIconWidth() - 8) {
+ title = title.substring(0, title.length() - 1);
+ }
+
+ if (fileName.equals(title)) {
+ // Center
+ g.drawString(fileName, 6 + (ImagesIcons.ThumbnailBlank.getIconWidth() - 2 - fontMetrics.stringWidth(title)) / 2, ImagesIcons.ThumbnailBlank
+ .getIconHeight() + 8 + fontMetrics.getAscent());
+ } else {
+ int dotsWidth = fontMetrics.stringWidth(DOTS);
+ while (fontMetrics.stringWidth(title) > ImagesIcons.ThumbnailBlank.getIconWidth() - 8 - dotsWidth) {
+ title = title.substring(0, title.length() - 1);
+ }
+ g.drawString(title + DOTS, 6, ImagesIcons.ThumbnailBlank.getIconHeight() + 8 + fontMetrics.getAscent());
+ }
+ }
+
+ private void paintFileSize(Graphics g, ThumbnailComponent tc) {
+ Font font = getSmallFont();
+ FontMetrics fontMetrics = g.getFontMetrics(font);
+ g.setColor(Color.BLACK);
+ g.setFont(font);
+ g.drawString(
+ tc.getFileSizeText(),
+ 8,
+ ImagesIcons.ThumbnailBlank.getIconHeight() + 4 - fontMetrics.getHeight() + fontMetrics.getAscent()
+ );
+ }
+
+ private void paintError(Graphics g, ThumbnailComponent tc) {
+ Font font = getSmallFont();
+ FontMetrics fontMetrics = g.getFontMetrics(font);
+
+ Messages.getErrorIcon().paintIcon(
+ tc,
+ g,
+ 5 + (ImagesIcons.ThumbnailBlank.getIconWidth() - Messages.getErrorIcon().getIconWidth()) / 2,
+ 5 + (ImagesIcons.ThumbnailBlank.getIconHeight() - Messages.getErrorIcon().getIconHeight()) / 2
+ );
+
+ // Error
+ String error = getSubmnailComponentErrorString();
+ g.setColor(JBColor.RED);
+ g.setFont(font);
+ g.drawString(error, 8, 8 + fontMetrics.getAscent());
+ }
+
+ private String getSubmnailComponentErrorString() {
+ return UIManager.getString(THUMBNAIL_COMPONENT_ERROR_STRING);
+ }
+
+ private static Font getSmallFont() {
+ Font labelFont = UIUtil.getLabelFont();
+ return labelFont.deriveFont(labelFont.getSize2D() - 2.0f);
+ }
+
+ public Dimension getPreferredSize(JComponent c) {
+ Font labelFont = UIUtil.getLabelFont();
+ FontMetrics fontMetrics = c.getFontMetrics(labelFont);
+ return new Dimension(
+ ImagesIcons.ThumbnailBlank.getIconWidth() + 10,
+ ImagesIcons.ThumbnailBlank.getIconHeight() + fontMetrics.getHeight() + 15
+ );
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static ComponentUI createUI(JComponent c) {
+ return ui;
+ }
+}
+
diff --git a/images/src/org/intellij/images/util/ImageInfoReader.java b/images/src/org/intellij/images/util/ImageInfoReader.java
new file mode 100644
index 000000000000..7dd764faa328
--- /dev/null
+++ b/images/src/org/intellij/images/util/ImageInfoReader.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.util;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.io.UnsyncByteArrayInputStream;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.*;
+
+/**
+ * @author spleaner
+ */
+public class ImageInfoReader {
+ private static final Logger LOG = Logger.getInstance("#org.intellij.images.util.ImageInfoReader");
+
+ private ImageInfoReader() {
+ }
+
+ @Nullable
+ public static Info getInfo(@NotNull final String file) {
+ return read(file);
+ }
+
+ @Nullable
+ public static Info getInfo(@NotNull final byte[] data) {
+ return read(data);
+ }
+
+ @Nullable
+ private static Info read(@NotNull final String file) {
+ final RandomAccessFile raf;
+ try {
+ //noinspection HardCodedStringLiteral
+ raf = new RandomAccessFile(file, "r");
+ try {
+ return readFileData(raf);
+ }
+ finally {
+ try {
+ raf.close();
+ }
+ catch (IOException e) {
+ // nothing
+ }
+ }
+ }
+ catch (IOException e) {
+ return null;
+ }
+ }
+
+ @Nullable
+ private static Info read(@NotNull final byte[] data) {
+ final DataInputStream is = new DataInputStream(new UnsyncByteArrayInputStream(data));
+ try {
+ return readFileData(is);
+ }
+ catch (IOException e) {
+ return null;
+ }
+ finally {
+ try {
+ is.close();
+ }
+ catch (IOException e) {
+ // nothing
+ }
+ }
+ }
+
+
+ @Nullable
+ private static Info readFileData(@NotNull final DataInput di) throws IOException {
+ final int b1 = di.readUnsignedByte();
+ final int b2 = di.readUnsignedByte();
+
+ if (b1 == 0x47 && b2 == 0x49) {
+ return readGif(di);
+ }
+
+ if (b1 == 0x89 && b2 == 0x50) {
+ return readPng(di);
+ }
+
+ if (b1 == 0xff && b2 == 0xd8) {
+ return readJpeg(di);
+ }
+
+ //if (b1 == 0x42 && b2 == 0x4d) {
+ // return readBmp(raf);
+ //}
+
+ return null;
+ }
+
+ @Nullable
+ private static Info readGif(DataInput di) throws IOException {
+ final byte[] GIF_MAGIC_87A = {0x46, 0x38, 0x37, 0x61};
+ final byte[] GIF_MAGIC_89A = {0x46, 0x38, 0x39, 0x61};
+ byte[] a = new byte[11]; // 4 from the GIF signature + 7 from the global header
+
+ di.readFully(a);
+ if ((!eq(a, 0, GIF_MAGIC_89A, 0, 4)) && (!eq(a, 0, GIF_MAGIC_87A, 0, 4))) {
+ return null;
+ }
+
+ final int width = getShortLittleEndian(a, 4);
+ final int height = getShortLittleEndian(a, 6);
+
+ int flags = a[8] & 0xff;
+ final int bpp = ((flags >> 4) & 0x07) + 1;
+
+ return new Info(width, height, bpp);
+ }
+
+ private static Info readBmp(RandomAccessFile raf) throws IOException {
+ byte[] a = new byte[44];
+ if (raf.read(a) != a.length) {
+ return null;
+ }
+
+ final int width = getIntLittleEndian(a, 16);
+ final int height = getIntLittleEndian(a, 20);
+
+ if (width < 1 || height < 1) {
+ return null;
+ }
+
+ final int bpp = getShortLittleEndian(a, 26);
+ if (bpp != 1 && bpp != 4 && bpp != 8 && bpp != 16 && bpp != 24 & bpp != 32) {
+ return null;
+ }
+
+ return new Info(width, height, bpp);
+ }
+
+ @Nullable
+ private static Info readJpeg(DataInput di) throws IOException {
+ byte[] a = new byte[13];
+ while (true) {
+ di.readFully(a, 0, 4);
+
+ int marker = getShortBigEndian(a, 0);
+ final int size = getShortBigEndian(a, 2);
+
+ if ((marker & 0xff00) != 0xff00) {
+ return null;
+ }
+
+ if (marker == 0xffe0) {
+ if (size < 14) {
+ di.skipBytes(size - 2);
+ continue;
+ }
+
+ di.readFully(a, 0, 12);
+ di.skipBytes(size - 14);
+ }
+ else if (marker >= 0xffc0 && marker <= 0xffcf && marker != 0xffc4 && marker != 0xffc8) {
+ di.readFully(a, 0, 6);
+
+ final int bpp = (a[0] & 0xff) * (a[5] & 0xff);
+ final int width = getShortBigEndian(a, 3);
+ final int height = getShortBigEndian(a, 1);
+
+ return new Info(width, height, bpp);
+ }
+ else {
+ di.skipBytes(size - 2);
+ }
+ }
+ }
+
+ @Nullable
+ private static Info readPng(DataInput di) throws IOException {
+ final byte[] PNG_MAGIC = {0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
+ byte[] a = new byte[27];
+
+ di.readFully(a);
+ if (!eq(a, 0, PNG_MAGIC, 0, 6)) {
+ return null;
+ }
+
+ final int width = getIntBigEndian(a, 14);
+ final int height = getIntBigEndian(a, 18);
+ int bpp = a[22] & 0xff;
+ int colorType = a[23] & 0xff;
+ if (colorType == 2 || colorType == 6) {
+ bpp *= 3;
+ }
+
+ return new Info(width, height, bpp);
+ }
+
+ private static int getShortBigEndian(byte[] a, int offset) {
+ return (a[offset] & 0xff) << 8 | (a[offset + 1] & 0xff);
+ }
+
+ private static boolean eq(byte[] a1, int offset1, byte[] a2, int offset2, int num) {
+ while (num-- > 0) {
+ if (a1[offset1++] != a2[offset2++]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static int getIntBigEndian(byte[] a, int offset) {
+ return (a[offset] & 0xff) << 24 | (a[offset + 1] & 0xff) << 16 | (a[offset + 2] & 0xff) << 8 | a[offset + 3] & 0xff;
+ }
+
+ private static int getIntLittleEndian(byte[] a, int offset) {
+ return (a[offset + 3] & 0xff) << 24 | (a[offset + 2] & 0xff) << 16 | (a[offset + 1] & 0xff) << 8 | a[offset] & 0xff;
+ }
+
+ private static int getShortLittleEndian(byte[] a, int offset) {
+ return (a[offset] & 0xff) | (a[offset + 1] & 0xff) << 8;
+ }
+
+ public static class Info {
+ public int width;
+ public int height;
+ public int bpp;
+
+ public Info(int width, int height, int bpp) {
+ this.width = width;
+ this.height = height;
+ this.bpp = bpp;
+ }
+ }
+
+}
diff --git a/images/src/org/intellij/images/util/imageio/SanselanImageReaderSpi.java b/images/src/org/intellij/images/util/imageio/SanselanImageReaderSpi.java
new file mode 100644
index 000000000000..c7136d54eeea
--- /dev/null
+++ b/images/src/org/intellij/images/util/imageio/SanselanImageReaderSpi.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2000-2009 JetBrains s.r.o.
+ *
+ * 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.util.imageio;
+
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.ArrayUtil;
+import org.apache.sanselan.ImageFormat;
+import org.apache.sanselan.ImageInfo;
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.Sanselan;
+import org.apache.sanselan.common.byteSources.ByteSource;
+
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+public class SanselanImageReaderSpi extends ImageReaderSpi {
+
+ private ThreadLocal<ImageFormat> myFormat = new ThreadLocal<ImageFormat>();
+
+ public SanselanImageReaderSpi() {
+ super();
+ vendorName = "JetBrains, s.r.o.";
+ version = "1.0";
+
+ // todo standard GIF/BMP formats can be optionally skipped as well
+ // JPEG is skipped due to Exception: Sanselan cannot read or write JPEG images. (JpegImageParser.java:92)
+ // tiff reader seems to be broken
+ // PNG reader has bugs with well-compressed PNG images, use standard one instead
+ final ArrayList<ImageFormat> imageFormats = new ArrayList<ImageFormat>(Arrays.asList(ImageFormat.getAllFormats()));
+ imageFormats.removeAll(Arrays.asList(ImageFormat.IMAGE_FORMAT_UNKNOWN,
+ ImageFormat.IMAGE_FORMAT_JPEG,
+ ImageFormat.IMAGE_FORMAT_TIFF,
+ ImageFormat.IMAGE_FORMAT_PNG));
+
+
+ names = new String[imageFormats.size() * 2];
+ suffixes = new String[imageFormats.size()];
+ MIMETypes = new String[imageFormats.size()];
+ pluginClassName = MyImageReader.class.getName();
+ inputTypes = new Class[] {ImageInputStream.class};
+ for (int i = 0, allFormatsLength = imageFormats.size(); i < allFormatsLength; i++) {
+ final ImageFormat format = imageFormats.get(i);
+ names[2 * i] = format.extension.toLowerCase();
+ names[2 * i + 1] = format.extension.toUpperCase();
+ suffixes[i] = names[2 * i];
+ MIMETypes[i] = "image/" + names[2 * i];
+ }
+ }
+
+ public String getDescription(Locale locale) {
+ return "Apache Sanselan project based image reader";
+ }
+
+ public boolean canDecodeInput(Object input) throws IOException {
+ if (!(input instanceof ImageInputStream)) {
+ return false;
+ }
+ final ImageInputStream stream = (ImageInputStream)input;
+ try {
+ final ImageFormat imageFormat = Sanselan.guessFormat(new MyByteSource(stream));
+ if (imageFormat != null && imageFormat != ImageFormat.IMAGE_FORMAT_JPEG) {
+ myFormat.set(imageFormat);
+ return true;
+ }
+ return false;
+ }
+ catch (ImageReadException e) {
+ throw new IOException(e);
+ }
+ }
+
+ public ImageReader createReaderInstance(Object extension) {
+ return new MyImageReader(this, myFormat.get());
+ }
+
+ private static class MyByteSource extends ByteSource {
+ private final ImageInputStream myStream;
+
+ public MyByteSource(final ImageInputStream stream) {
+ super(stream.toString());
+ myStream = stream;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ myStream.seek(0);
+ return new InputStream() {
+ @Override
+ public int read() throws IOException {
+ return myStream.read();
+ }
+
+ @Override
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ return myStream.read(b, off, len);
+ }
+ };
+ }
+
+ @Override
+ public byte[] getBlock(final int start, final int length) throws IOException {
+ myStream.seek(start);
+ final byte[] bytes = new byte[length];
+ final int read = myStream.read(bytes);
+ return ArrayUtil.realloc(bytes, read);
+ }
+
+ @Override
+ public byte[] getAll() throws IOException {
+ return FileUtil.loadBytes(getInputStream());
+ }
+
+ @Override
+ public long getLength() throws IOException {
+ return myStream.length();
+ }
+
+ @Override
+ public String getDescription() {
+ return myStream.toString();
+ }
+ }
+
+ private static class MyImageReader extends ImageReader {
+ private byte[] myBytes;
+ private ImageInfo myInfo;
+ private BufferedImage[] myImages;
+ private final ImageFormat myDefaultFormat;
+
+ public MyImageReader(final SanselanImageReaderSpi provider, final ImageFormat imageFormat) {
+ super(provider);
+ myDefaultFormat = imageFormat == null? ImageFormat.IMAGE_FORMAT_UNKNOWN : imageFormat;
+ }
+
+ @Override
+ public void dispose() {
+ myBytes = null;
+ myInfo = null;
+ myImages = null;
+ }
+
+ @Override
+ public void setInput(final Object input, final boolean seekForwardOnly, final boolean ignoreMetadata) {
+ super.setInput(input, seekForwardOnly, ignoreMetadata);
+ myBytes = null;
+ myInfo = null;
+ myImages = null;
+ }
+
+ private ImageInfo getInfo() throws IOException {
+ if (myInfo == null) {
+ try {
+ myInfo = Sanselan.getImageInfo(getBytes());
+ }
+ catch (ImageReadException e) {
+ throw new IOException(e);
+ }
+ }
+ return myInfo;
+ }
+
+ private byte[] getBytes() throws IOException {
+ if (myBytes == null) {
+ final ImageInputStream stream = (ImageInputStream)input;
+ myBytes = new MyByteSource(stream).getAll();
+ }
+ return myBytes;
+ }
+
+ private BufferedImage[] getImages() throws IOException {
+ if (myImages == null) {
+ try {
+ final ArrayList<BufferedImage> images = Sanselan.getAllBufferedImages(getBytes());
+ myImages = images.toArray(new BufferedImage[images.size()]);
+ }
+ catch (ImageReadException e) {
+ throw new IOException(e);
+ }
+ }
+ return myImages;
+ }
+
+ @Override
+ public int getNumImages(final boolean allowSearch) throws IOException {
+ return getInfo().getNumberOfImages();
+ }
+
+ @Override
+ public int getWidth(final int imageIndex) throws IOException {
+ return getInfo().getWidth();
+ }
+
+ @Override
+ public int getHeight(final int imageIndex) throws IOException {
+ return getInfo().getHeight();
+ }
+
+ @Override
+ public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
+ return Collections.singletonList(ImageTypeSpecifier.createFromRenderedImage(getImages()[imageIndex])).iterator();
+ }
+
+ @Override
+ public IIOMetadata getStreamMetadata() throws IOException {
+ return null;
+ }
+
+ @Override
+ public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
+ return null;
+ }
+
+ @Override
+ public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
+ return getImages()[imageIndex];
+ }
+
+ @Override
+ public String getFormatName() throws IOException {
+ // return default if called before setInput
+ return input == null? myDefaultFormat.name : getInfo().getFormat().name;
+ }
+ }
+}
diff --git a/images/src/org/intellij/images/vfs/IfsUtil.java b/images/src/org/intellij/images/vfs/IfsUtil.java
new file mode 100644
index 000000000000..bb2f1541f299
--- /dev/null
+++ b/images/src/org/intellij/images/vfs/IfsUtil.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * 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.
+ */
+
+/** $Id$ */
+
+package org.intellij.images.vfs;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.reference.SoftReference;
+import com.intellij.util.LogicalRoot;
+import com.intellij.util.LogicalRootsManager;
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.common.byteSources.ByteSourceArray;
+import org.apache.sanselan.formats.ico.IcoImageParser;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+
+/**
+ * Image loader utility.
+ *
+ * @author <a href="mailto:aefimov.box@gmail.com">Alexey Efimov</a>
+ */
+public final class IfsUtil {
+ public static final String ICO_FORMAT = "ico";
+
+ private static final Key<Long> TIMESTAMP_KEY = Key.create("Image.timeStamp");
+ private static final Key<String> FORMAT_KEY = Key.create("Image.format");
+ private static final Key<SoftReference<BufferedImage>> BUFFERED_IMAGE_REF_KEY = Key.create("Image.bufferedImage");
+ private static final IcoImageParser ICO_IMAGE_PARSER = new IcoImageParser();
+
+ /**
+ * Load image data for file and put user data attributes into file.
+ *
+ * @param file File
+ * @return true if file image is loaded.
+ * @throws java.io.IOException if image can not be loaded
+ */
+ private static boolean refresh(@NotNull VirtualFile file) throws IOException {
+ Long loadedTimeStamp = file.getUserData(TIMESTAMP_KEY);
+ SoftReference<BufferedImage> imageRef = file.getUserData(BUFFERED_IMAGE_REF_KEY);
+ if (loadedTimeStamp == null || loadedTimeStamp.longValue() != file.getTimeStamp() || imageRef == null || imageRef.get() == null) {
+ try {
+ final byte[] content = file.contentsToByteArray();
+
+ if (ICO_FORMAT.equalsIgnoreCase(file.getExtension())) {
+ try {
+ final BufferedImage image = ICO_IMAGE_PARSER.getBufferedImage(new ByteSourceArray(content), null);
+ file.putUserData(FORMAT_KEY, ICO_FORMAT);
+ file.putUserData(BUFFERED_IMAGE_REF_KEY, new SoftReference<BufferedImage>(image));
+ return true;
+ }
+ catch (ImageReadException ignore) { }
+ }
+
+ InputStream inputStream = new ByteArrayInputStream(content, 0, content.length);
+ ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputStream);
+ try {
+ Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
+ if (imageReaders.hasNext()) {
+ ImageReader imageReader = imageReaders.next();
+ try {
+ file.putUserData(FORMAT_KEY, imageReader.getFormatName());
+ ImageReadParam param = imageReader.getDefaultReadParam();
+ imageReader.setInput(imageInputStream, true, true);
+ int minIndex = imageReader.getMinIndex();
+ BufferedImage image = imageReader.read(minIndex, param);
+ file.putUserData(BUFFERED_IMAGE_REF_KEY, new SoftReference<BufferedImage>(image));
+ return true;
+ } finally {
+ imageReader.dispose();
+ }
+ }
+ } finally {
+ imageInputStream.close();
+ }
+ } finally {
+ // We perform loading no more needed
+ file.putUserData(TIMESTAMP_KEY, file.getTimeStamp());
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ public static BufferedImage getImage(@NotNull VirtualFile file) throws IOException {
+ refresh(file);
+ SoftReference<BufferedImage> imageRef = file.getUserData(BUFFERED_IMAGE_REF_KEY);
+ return imageRef != null ? imageRef.get() : null;
+ }
+
+ @Nullable
+ public static String getFormat(@NotNull VirtualFile file) throws IOException {
+ refresh(file);
+ return file.getUserData(FORMAT_KEY);
+ }
+
+ public static String getReferencePath(Project project, VirtualFile file) {
+ final LogicalRoot logicalRoot = LogicalRootsManager.getLogicalRootsManager(project).findLogicalRoot(file);
+ if (logicalRoot != null) {
+ return getRelativePath(file, logicalRoot.getVirtualFile());
+ }
+
+ ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
+ VirtualFile sourceRoot = fileIndex.getSourceRootForFile(file);
+ if (sourceRoot != null) {
+ return getRelativePath(file, sourceRoot);
+ }
+
+ VirtualFile root = fileIndex.getContentRootForFile(file);
+ if (root != null) {
+ return getRelativePath(file, root);
+ }
+
+ return file.getPath();
+ }
+
+ private static String getRelativePath(final VirtualFile file, final VirtualFile root) {
+ if (root.equals(file)) {
+ return file.getPath();
+ }
+ return "/" + VfsUtilCore.getRelativePath(file, root, '/');
+ }
+}