diff --git a/platform/core-api/src/com/intellij/ui/RowIcon.java b/platform/core-api/src/com/intellij/ui/RowIcon.java index 6449700a550d..12ae5dedef4c 100644 --- a/platform/core-api/src/com/intellij/ui/RowIcon.java +++ b/platform/core-api/src/com/intellij/ui/RowIcon.java @@ -1,4 +1,3 @@ - /* * Copyright 2000-2015 JetBrains s.r.o. * @@ -17,9 +16,14 @@ package com.intellij.ui; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + import javax.swing.*; import java.awt.*; import java.util.Arrays; +import java.util.List; public class RowIcon implements Icon { private final Alignment myAlignment; @@ -46,6 +50,13 @@ public class RowIcon implements Icon { recalculateSize(); } + @TestOnly + @NotNull + Icon[] getAllIcons() { + List icons = ContainerUtil.packNullables(myIcons); + return icons.toArray(new Icon[icons.size()]); + } + public int hashCode() { return myIcons.length > 0 ? myIcons[0].hashCode() : 0; } diff --git a/platform/core-api/src/com/intellij/util/IconUtil.java b/platform/core-api/src/com/intellij/util/IconUtil.java index 4cf9f85a9fb8..f72a8aae141a 100644 --- a/platform/core-api/src/com/intellij/util/IconUtil.java +++ b/platform/core-api/src/com/intellij/util/IconUtil.java @@ -27,7 +27,6 @@ import com.intellij.openapi.util.Key; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vfs.VFileProperty; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.WritingAccessProvider; import com.intellij.ui.IconDeferrer; import com.intellij.ui.JBColor; import com.intellij.ui.LayeredIcon; @@ -53,11 +52,11 @@ public class IconUtil { Boolean was = project.getUserData(PROJECT_WAS_EVER_INITIALIZED); if (was == null) { if (project.isInitialized()) { - was = Boolean.valueOf(true); + was = true; project.putUserData(PROJECT_WAS_EVER_INITIALIZED, was); } else { - was = Boolean.valueOf(false); + was = false; } } @@ -94,7 +93,8 @@ public class IconUtil { return new ImageIcon(img); } - public static Icon cropIcon(@NotNull Icon icon, Rectangle area) { + @NotNull + public static Icon cropIcon(@NotNull Icon icon, @NotNull Rectangle area) { if (!new Rectangle(icon.getIconWidth(), icon.getIconHeight()).contains(area)) { return icon; } @@ -153,10 +153,6 @@ public class IconUtil { icon = patcher.patchIcon(icon, file, flags, project); } - if ((flags & Iconable.ICON_FLAG_READ_STATUS) != 0 && - (!file.isWritable() || !WritingAccessProvider.isPotentiallyWritable(file, project))) { - icon = new LayeredIcon(icon, PlatformIcons.LOCKED_ICON); - } if (file.is(VFileProperty.SYMLINK)) { icon = new LayeredIcon(icon, PlatformIcons.SYMLINK_ICON); } @@ -234,72 +230,88 @@ public class IconUtil { } } + @NotNull public static Icon getAddIcon() { return getToolbarDecoratorIcon("add.png"); } + @NotNull public static Icon getRemoveIcon() { return getToolbarDecoratorIcon("remove.png"); } + @NotNull public static Icon getMoveUpIcon() { return getToolbarDecoratorIcon("moveUp.png"); } + @NotNull public static Icon getMoveDownIcon() { return getToolbarDecoratorIcon("moveDown.png"); } + @NotNull public static Icon getEditIcon() { return getToolbarDecoratorIcon("edit.png"); } + @NotNull public static Icon getAddClassIcon() { return getToolbarDecoratorIcon("addClass.png"); } + @NotNull public static Icon getAddPatternIcon() { return getToolbarDecoratorIcon("addPattern.png"); } + @NotNull public static Icon getAddJiraPatternIcon() { return getToolbarDecoratorIcon("addJira.png"); } + @NotNull public static Icon getAddYouTrackPatternIcon() { return getToolbarDecoratorIcon("addYouTrack.png"); } + @NotNull public static Icon getAddBlankLineIcon() { return getToolbarDecoratorIcon("addBlankLine.png"); } + @NotNull public static Icon getAddPackageIcon() { return getToolbarDecoratorIcon("addPackage.png"); } + @NotNull public static Icon getAddLinkIcon() { return getToolbarDecoratorIcon("addLink.png"); } + @NotNull public static Icon getAddFolderIcon() { return getToolbarDecoratorIcon("addFolder.png"); } + @NotNull public static Icon getAnalyzeIcon() { return getToolbarDecoratorIcon("analyze.png"); } - public static void paintInCenterOf(@NotNull Component c, Graphics g, Icon icon) { + public static void paintInCenterOf(@NotNull Component c, @NotNull Graphics g, @NotNull Icon icon) { final int x = (c.getWidth() - icon.getIconWidth()) / 2; final int y = (c.getHeight() - icon.getIconHeight()) / 2; icon.paintIcon(c, g, x, y); } - public static Icon getToolbarDecoratorIcon(String name) { + @NotNull + private static Icon getToolbarDecoratorIcon(@NotNull String name) { return IconLoader.getIcon(getToolbarDecoratorIconsFolder() + name); } + @NotNull private static String getToolbarDecoratorIconsFolder() { return "/toolbarDecorator/" + (SystemInfo.isMac ? "mac/" : ""); } @@ -322,6 +334,7 @@ public class IconUtil { return result; } + @NotNull public static Icon toSize(@NotNull Icon icon, int width, int height) { return new IconSizeWrapper(icon, width, height); } @@ -359,7 +372,7 @@ public class IconUtil { private final Icon mySrc; private final Rectangle myCrop; - private CropIcon(@NotNull Icon src, Rectangle crop) { + private CropIcon(@NotNull Icon src, @NotNull Rectangle crop) { mySrc = src; myCrop = crop; } @@ -380,12 +393,14 @@ public class IconUtil { } } + @NotNull public static Icon scale(@NotNull final Icon source, double _scale) { final int hiDPIscale; if (source instanceof ImageIcon) { Image image = ((ImageIcon)source).getImage(); hiDPIscale = RetinaImage.isAppleHiDPIScaledImage(image) || image instanceof JBHiDPIScaledImage ? 2 : 1; - } else { + } + else { hiDPIscale = 1; } final double scale = Math.min(32, Math.max(.1, _scale)); @@ -400,7 +415,8 @@ public class IconUtil { g2d.setTransform(transform); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); source.paintIcon(c, g2d, 0, 0); - } finally { + } + finally { g2d.dispose(); } } @@ -415,7 +431,6 @@ public class IconUtil { return (int)(source.getIconHeight() * scale) / hiDPIscale; } }; - } @NotNull @@ -465,7 +480,7 @@ public class IconUtil { } @NotNull - public static Icon textToIcon(final String text, final Component component, final float fontSize) { + public static Icon textToIcon(@NotNull final String text, @NotNull final Component component, final float fontSize) { final Font font = JBFont.create(JBUI.Fonts.label().deriveFont(fontSize)); FontMetrics metrics = component.getFontMetrics(font); final int width = metrics.stringWidth(text) + JBUI.scale(4); @@ -479,7 +494,8 @@ public class IconUtil { GraphicsUtil.setupAntialiasing(g); g.setFont(font); UIUtil.drawStringWithHighlighting(g, text, x + JBUI.scale(2), y + height - JBUI.scale(1), JBColor.foreground(), JBColor.background()); - } finally { + } + finally { g.dispose(); } } @@ -496,6 +512,7 @@ public class IconUtil { }; } + @NotNull public static Icon addText(@NotNull Icon base, @NotNull String text) { LayeredIcon icon = new LayeredIcon(2); icon.setIcon(base, 0); diff --git a/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java b/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java index c31932cbb8bf..420eb81b961f 100644 --- a/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java +++ b/platform/lang-impl/src/com/intellij/ui/DeferredIconImpl.java @@ -35,6 +35,7 @@ import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import org.jetbrains.ide.PooledThreadExecutor; import javax.swing.*; @@ -371,6 +372,12 @@ public class DeferredIconImpl implements DeferredIcon { equalIcons(myDelegateIcon, ((DeferredIconImpl)icon).myDelegateIcon); } + @TestOnly + @NotNull + Icon getDelegateIcon() { + return myDelegateIcon; + } + @Override public String toString() { return "Deferred. Base=" + myDelegateIcon; diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/IconUtilTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/IconUtilTest.java deleted file mode 100644 index 16f9ed46c69d..000000000000 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/IconUtilTest.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2000-2015 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 com.intellij.openapi.editor.impl; - -import com.intellij.openapi.util.Iconable; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.testFramework.PlatformTestCase; -import com.intellij.ui.DeferredIcon; -import com.intellij.util.FileContentUtilCore; -import com.intellij.util.IconUtil; -import com.intellij.util.TimeoutUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.image.ImageObserver; -import java.io.IOException; -import java.text.AttributedCharacterIterator; - -public class IconUtilTest extends PlatformTestCase { - public void testIconDeferrerDoesNotDeferIconsAdInfinitum() throws IOException { - VirtualFile file = createTempFile("txt", null, "hkjh", CharsetToolkit.UTF8_CHARSET); - - Icon icon = IconUtil.getIcon(file, Iconable.ICON_FLAG_VISIBILITY, getProject()); - assertTrue(icon instanceof DeferredIcon); - - Graphics g = new Graphics() { - @Override - public Graphics create() { - return this; - } - - @Override - public void translate(int x, int y) { - - } - - @Override - public Color getColor() { - return null; - } - - @Override - public void setColor(Color c) { - - } - - @Override - public void setPaintMode() { - - } - - @Override - public void setXORMode(Color c1) { - - } - - @Override - public Font getFont() { - return null; - } - - @Override - public void setFont(Font font) { - - } - - @Override - public FontMetrics getFontMetrics(Font f) { - return null; - } - - @Override - public Rectangle getClipBounds() { - return null; - } - - @Override - public void clipRect(int x, int y, int width, int height) { - - } - - @Override - public void setClip(int x, int y, int width, int height) { - - } - - @Override - public Shape getClip() { - return null; - } - - @Override - public void setClip(Shape clip) { - - } - - @Override - public void copyArea(int x, int y, int width, int height, int dx, int dy) { - - } - - @Override - public void drawLine(int x1, int y1, int x2, int y2) { - - } - - @Override - public void fillRect(int x, int y, int width, int height) { - - } - - @Override - public void clearRect(int x, int y, int width, int height) { - - } - - @Override - public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { - - } - - @Override - public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { - - } - - @Override - public void drawOval(int x, int y, int width, int height) { - - } - - @Override - public void fillOval(int x, int y, int width, int height) { - - } - - @Override - public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { - - } - - @Override - public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { - - } - - @Override - public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { - - } - - @Override - public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { - - } - - @Override - public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { - - } - - @Override - public void drawString(String str, int x, int y) { - - } - - @Override - public void drawString(AttributedCharacterIterator iterator, int x, int y) { - - } - - @Override - public boolean drawImage(Image img, int x, int y, ImageObserver observer) { - return false; - } - - @Override - public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { - return false; - } - - @Override - public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { - return false; - } - - @Override - public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { - return false; - } - - @Override - public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { - return false; - } - - @Override - public boolean drawImage(Image img, - int dx1, - int dy1, - int dx2, - int dy2, - int sx1, - int sy1, - int sx2, - int sy2, - Color bgcolor, - ImageObserver observer) { - return false; - } - - @Override - public void dispose() { - - } - }; - icon.paintIcon(new JLabel(), g, 0, 0); // force to eval - TimeoutUtil.sleep(1000); // give chance to evaluate - - Icon icon2 = IconUtil.getIcon(file, Iconable.ICON_FLAG_VISIBILITY, getProject()); - assertSame(icon, icon2); - - FileContentUtilCore.reparseFiles(file); - Icon icon3 = IconUtil.getIcon(file, Iconable.ICON_FLAG_VISIBILITY, getProject()); - assertNotSame(icon2, icon3); - } - - @Override - protected boolean isRunInWriteAction() { - return false; - } -} diff --git a/platform/platform-tests/testSrc/com/intellij/ui/IconUtilTest.java b/platform/platform-tests/testSrc/com/intellij/ui/IconUtilTest.java new file mode 100644 index 000000000000..6235c05caf18 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/ui/IconUtilTest.java @@ -0,0 +1,309 @@ +/* + * Copyright 2000-2015 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 com.intellij.ui; + +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.util.ThrowableComputable; +import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.testFramework.PsiTestUtil; +import com.intellij.util.FileContentUtilCore; +import com.intellij.util.IconUtil; +import com.intellij.util.PlatformIcons; +import com.intellij.util.TimeoutUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.ImageObserver; +import java.io.File; +import java.io.IOException; +import java.text.AttributedCharacterIterator; +import java.util.Collections; +import java.util.List; + +public class IconUtilTest extends PlatformTestCase { + public void testIconDeferrerDoesNotDeferIconsAdInfinitum() throws IOException { + VirtualFile file = createTempFile("txt", null, "hkjh", CharsetToolkit.UTF8_CHARSET); + + Icon icon = IconUtil.getIcon(file, Iconable.ICON_FLAG_VISIBILITY, getProject()); + assertTrue(icon instanceof DeferredIcon); + + Graphics g = createMockGraphics(); + icon.paintIcon(new JLabel(), g, 0, 0); // force to eval + TimeoutUtil.sleep(1000); // give chance to evaluate + + Icon icon2 = IconUtil.getIcon(file, Iconable.ICON_FLAG_VISIBILITY, getProject()); + assertSame(icon, icon2); + + FileContentUtilCore.reparseFiles(file); + Icon icon3 = IconUtil.getIcon(file, Iconable.ICON_FLAG_VISIBILITY, getProject()); + assertNotSame(icon2, icon3); + } + + @NotNull + private static Graphics createMockGraphics() { + return new Graphics() { + @Override + public Graphics create() { + return this; + } + + @Override + public void translate(int x, int y) { + + } + + @Override + public Color getColor() { + return null; + } + + @Override + public void setColor(Color c) { + + } + + @Override + public void setPaintMode() { + + } + + @Override + public void setXORMode(Color c1) { + + } + + @Override + public Font getFont() { + return null; + } + + @Override + public void setFont(Font font) { + + } + + @Override + public FontMetrics getFontMetrics(Font f) { + return null; + } + + @Override + public Rectangle getClipBounds() { + return null; + } + + @Override + public void clipRect(int x, int y, int width, int height) { + + } + + @Override + public void setClip(int x, int y, int width, int height) { + + } + + @Override + public Shape getClip() { + return null; + } + + @Override + public void setClip(Shape clip) { + + } + + @Override + public void copyArea(int x, int y, int width, int height, int dx, int dy) { + + } + + @Override + public void drawLine(int x1, int y1, int x2, int y2) { + + } + + @Override + public void fillRect(int x, int y, int width, int height) { + + } + + @Override + public void clearRect(int x, int y, int width, int height) { + + } + + @Override + public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { + + } + + @Override + public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { + + } + + @Override + public void drawOval(int x, int y, int width, int height) { + + } + + @Override + public void fillOval(int x, int y, int width, int height) { + + } + + @Override + public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { + + } + + @Override + public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { + + } + + @Override + public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { + + } + + @Override + public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { + + } + + @Override + public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { + + } + + @Override + public void drawString(String str, int x, int y) { + + } + + @Override + public void drawString(AttributedCharacterIterator iterator, int x, int y) { + + } + + @Override + public boolean drawImage(Image img, int x, int y, ImageObserver observer) { + return false; + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { + return false; + } + + @Override + public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { + return false; + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { + return false; + } + + @Override + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { + return false; + } + + @Override + public boolean drawImage(Image img, + int dx1, + int dy1, + int dx2, + int dy2, + int sx1, + int sy1, + int sx2, + int sy2, + Color bgcolor, + ImageObserver observer) { + return false; + } + + @Override + public void dispose() { + + } + }; + } + + public void testLockedPatchSmallIconAppliedOnlyOnce() throws IOException { + File dir = createTempDir("my"); + + VirtualFile sourceRoot = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(dir); + VirtualFile file = createChildData(sourceRoot, "X.java"); + + PsiTestUtil.addSourceRoot(getModule(), sourceRoot); + WriteCommandAction.runWriteCommandAction(getProject(), + (ThrowableComputable)() -> { + file.setBinaryContent("class X {}".getBytes(CharsetToolkit.UTF8_CHARSET)); + file.setWritable(false); + return null; + }); + UIUtil.dispatchAllInvocationEvents(); // write actions + UIUtil.dispatchAllInvocationEvents(); + try { + Icon icon = IconUtil.getIcon(file, -1, getProject()); + icon.paintIcon(new JLabel(), createMockGraphics(), 0, 0); // force to eval + TimeoutUtil.sleep(1000); // give chance to evaluate + UIUtil.dispatchAllInvocationEvents(); + UIUtil.dispatchAllInvocationEvents(); + + List icons = autopsyIconsFrom(icon); + assertOneElement(ContainerUtil.filter(icons, ic -> ic == PlatformIcons.LOCKED_ICON)); + } + finally { + WriteCommandAction.runWriteCommandAction(getProject(), + (ThrowableComputable)() -> { + file.setWritable(true); + return null; + }); + } + } + + @NotNull + private static List autopsyIconsFrom(@NotNull Icon icon) { + if (icon instanceof DeferredIconImpl) { + return autopsyIconsFrom(((DeferredIconImpl)icon).getDelegateIcon()); + } + if (icon instanceof LayeredIcon) { + return ContainerUtil.flatten(ContainerUtil.map(((LayeredIcon)icon).getAllLayers(), IconUtilTest::autopsyIconsFrom)); + } + if (icon instanceof RowIcon) { + return ContainerUtil.flatten(ContainerUtil.map(((RowIcon)icon).getAllIcons(), IconUtilTest::autopsyIconsFrom)); + } + return Collections.singletonList(icon); + } + + @Override + protected boolean isRunInWriteAction() { + return false; + } +}