diff options
author | Jean-Baptiste Queru <jbq@google.com> | 2013-01-08 11:11:20 -0800 |
---|---|---|
committer | Jean-Baptiste Queru <jbq@google.com> | 2013-01-08 11:11:20 -0800 |
commit | b56ea2a18f232d79481e778085fd64e8ae486fc3 (patch) | |
tree | 44e1f6eb4864a45033f865b74fe783e3d784dd6a /images | |
download | idea-b56ea2a18f232d79481e778085fd64e8ae486fc3.tar.gz |
Snapshot of commit d5ec1d5018ed24f1b4f32b1d09df6dbd7e2fc425
from branch master of git://git.jetbrains.org/idea/community.git
Diffstat (limited to 'images')
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 Binary files differnew file mode 100644 index 000000000000..23e5ce91e5b6 --- /dev/null +++ b/images/src/org/intellij/images/icons/EditExternaly.png diff --git a/images/src/org/intellij/images/icons/ImagesFileType.png b/images/src/org/intellij/images/icons/ImagesFileType.png Binary files differnew file mode 100644 index 000000000000..918fa6233f42 --- /dev/null +++ b/images/src/org/intellij/images/icons/ImagesFileType.png diff --git a/images/src/org/intellij/images/icons/ThumbnailBlank.png b/images/src/org/intellij/images/icons/ThumbnailBlank.png Binary files differnew file mode 100644 index 000000000000..53deaf8f77a4 --- /dev/null +++ b/images/src/org/intellij/images/icons/ThumbnailBlank.png diff --git a/images/src/org/intellij/images/icons/ThumbnailDirectory.png b/images/src/org/intellij/images/icons/ThumbnailDirectory.png Binary files differnew file mode 100644 index 000000000000..7d4afcc7423a --- /dev/null +++ b/images/src/org/intellij/images/icons/ThumbnailDirectory.png diff --git a/images/src/org/intellij/images/icons/ThumbnailToolWindow.png b/images/src/org/intellij/images/icons/ThumbnailToolWindow.png Binary files differnew file mode 100644 index 000000000000..377a001dd602 --- /dev/null +++ b/images/src/org/intellij/images/icons/ThumbnailToolWindow.png diff --git a/images/src/org/intellij/images/icons/ToggleGrid.png b/images/src/org/intellij/images/icons/ToggleGrid.png Binary files differnew file mode 100644 index 000000000000..badec3ac01b8 --- /dev/null +++ b/images/src/org/intellij/images/icons/ToggleGrid.png diff --git a/images/src/org/intellij/images/icons/ToggleTransparencyChessboard.png b/images/src/org/intellij/images/icons/ToggleTransparencyChessboard.png Binary files differnew file mode 100644 index 000000000000..032f07149032 --- /dev/null +++ b/images/src/org/intellij/images/icons/ToggleTransparencyChessboard.png 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, '/'); + } +} |