From c82f50d8dff986b657c77ed0639786a1bf8be9c9 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Sun, 12 Mar 2023 10:23:10 +0100 Subject: [PATCH] IconManager.getIcon should return the same instance for the same input args as soon as cache is valid (not invalidated due time- or size- or access- eviction policy) GitOrigin-RevId: 1a39cd16087772d27eb42a5f8c76b852159e8656 --- .../core-ui/src/ui/icons/CoreIconManager.kt | 49 ++++---- .../icons/ScalableIconWrapperWithToolTip.java | 34 ------ .../src/com/intellij/ide/ui/UITheme.java | 2 +- .../util/src/com/intellij/ui/IconManager.kt | 5 + .../com/intellij/openapi/util/IconLoader.kt | 109 +++++++++--------- .../com/intellij/ui/icons/CachedImageIcon.kt | 17 ++- .../intellij}/ui/icons/IconWithToolTip.java | 3 +- .../ui/icons/ImageDataByPathLoader.kt | 43 +++---- .../intellij/ui/icons/ImageDataByUrlLoader.kt | 22 +--- 9 files changed, 126 insertions(+), 158 deletions(-) delete mode 100644 platform/core-ui/src/ui/icons/ScalableIconWrapperWithToolTip.java rename platform/{core-ui/src => util/ui/src/com/intellij}/ui/icons/IconWithToolTip.java (84%) diff --git a/platform/core-ui/src/ui/icons/CoreIconManager.kt b/platform/core-ui/src/ui/icons/CoreIconManager.kt index 9b3289751d0b..552659af1e64 100644 --- a/platform/core-ui/src/ui/icons/CoreIconManager.kt +++ b/platform/core-ui/src/ui/icons/CoreIconManager.kt @@ -1,4 +1,6 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:Suppress("LiftReturnOrAssignment") + package com.intellij.ui.icons import com.intellij.AbstractBundle @@ -8,7 +10,9 @@ import com.intellij.icons.AllIcons import com.intellij.ide.IconLayerProvider import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project -import com.intellij.openapi.util.* +import com.intellij.openapi.util.Iconable +import com.intellij.openapi.util.findIconUsingDeprecatedImplementation +import com.intellij.openapi.util.findIconUsingNewImplementation import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.* @@ -27,6 +31,7 @@ import java.util.function.Function import java.util.function.Supplier import javax.swing.Icon +@Suppress("DeprecatedCallableAddReplaceWith") @ApiStatus.Internal class CoreIconManager : IconManager, CoreAwareIconManager { override fun getPlatformIcon(id: PlatformIcons): Icon { @@ -88,19 +93,25 @@ class CoreIconManager : IconManager, CoreAwareIconManager { } } + @Deprecated("Use getIcon(path, classLoader)") override fun getIcon(path: String, aClass: Class<*>): Icon { - val icon = IconLoader.getIcon(path, aClass) - val tooltip = IconDescriptionLoader(path) - return if (icon is ScalableIcon) ScalableIconWrapperWithToolTip(icon, tooltip) else IconWrapperWithToolTip( - icon, tooltip) + return findIconUsingDeprecatedImplementation(originalPath = path, + classLoader = aClass.classLoader, + aClass = aClass, + toolTip = IconDescriptionLoader(path))!! + } + + override fun getIcon(path: String, classLoader: ClassLoader): Icon { + return findIconUsingNewImplementation(path = path, classLoader = classLoader, toolTip = IconDescriptionLoader(path))!! } override fun loadRasterizedIcon(path: String, classLoader: ClassLoader, cacheKey: Int, flags: Int): Icon { - assert(!path.isEmpty() && path[0] != '/') - return IconWithToolTipImpl(originalPath = path, resolver = createRasterizedImageDataLoader(path = path, - classLoader = classLoader, - cacheKey = cacheKey, - imageFlags = flags)) + assert(!path.startsWith('/')) + return CachedImageIcon( + originalPath = path, + resolver = createRasterizedImageDataLoader(path = path, classLoader = classLoader, cacheKey = cacheKey, imageFlags = flags), + toolTip = IconDescriptionLoader(path), + ) } override fun createEmptyIcon(icon: Icon): Icon = EmptyIcon.create(icon) @@ -170,22 +181,6 @@ class CoreIconManager : IconManager, CoreAwareIconManager { override fun withIconBadge(icon: Icon, color: Paint): Icon = BadgeIcon(icon, color) } -private class IconWithToolTipImpl(originalPath: String, - resolver: ImageDataLoader) : CachedImageIcon(originalPath = originalPath, - resolver = resolver), IconWithToolTip { - @NlsSafe - private var result: String? = null - private var isTooltipCalculated = false - - override fun getToolTip(composite: Boolean): @NlsContexts.Tooltip String? { - if (!isTooltipCalculated) { - result = findIconDescription(originalPath!!) - isTooltipCalculated = true - } - return result - } -} - private class IconLayer(@JvmField val flagMask: Int, @JvmField val icon: Icon) { init { BitUtil.assertOneBitMask(flagMask) @@ -211,7 +206,7 @@ private const val FLAGS_LOCKED = 0x800 // a reflective path is not supported, a result is not cached private fun createRasterizedImageDataLoader(path: String, classLoader: ClassLoader, cacheKey: Int, imageFlags: Int): ImageDataLoader { val startTime = StartUpMeasurer.getCurrentTimeIfEnabled() - val patchedPath = IconLoader.patchPath(path, classLoader) + val patchedPath = CachedImageIcon.patchPath(originalPath = path, classLoader = classLoader) val classLoaderWeakRef = WeakReference(classLoader) val resolver = if (patchedPath == null) { RasterizedImageDataLoader(path = path, diff --git a/platform/core-ui/src/ui/icons/ScalableIconWrapperWithToolTip.java b/platform/core-ui/src/ui/icons/ScalableIconWrapperWithToolTip.java deleted file mode 100644 index 34b87eeeab60..000000000000 --- a/platform/core-ui/src/ui/icons/ScalableIconWrapperWithToolTip.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.ui.icons; - -import com.intellij.openapi.util.NlsContexts; -import com.intellij.openapi.util.ScalableIcon; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.util.function.Supplier; - -final class ScalableIconWrapperWithToolTip extends IconWrapperWithToolTip implements ScalableIcon { - ScalableIconWrapperWithToolTip(@SuppressWarnings("TypeMayBeWeakened") @NotNull ScalableIcon icon, @NotNull Supplier<@NlsContexts.Tooltip String> toolTip) { - super(icon, toolTip); - } - - private ScalableIconWrapperWithToolTip(IconWrapperWithToolTip another) { - super(another); - } - - @Override - public float getScale() { - return ((ScalableIcon)retrieveIcon()).getScale(); - } - - @Override - public @NotNull Icon scale(float scaleFactor) { - return ((ScalableIcon)retrieveIcon()).scale(scaleFactor); - } - - @Override - public @NotNull ScalableIconWrapperWithToolTip replaceBy(@NotNull IconReplacer replacer) { - return new ScalableIconWrapperWithToolTip(super.replaceBy(replacer)); - } -} diff --git a/platform/platform-impl/src/com/intellij/ide/ui/UITheme.java b/platform/platform-impl/src/com/intellij/ide/ui/UITheme.java index a8821eb0a41f..d69d1cbfd487 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/UITheme.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/UITheme.java @@ -600,7 +600,7 @@ public final class UITheme { } if (value.endsWith(".png") || value.endsWith(".svg")) { - Icon icon = ImageDataByPathLoader.Companion.findIcon(value, classLoader, null); + Icon icon = ImageDataByPathLoader.Companion.findIconByPath(value, classLoader, null, null); if (icon != null) { return icon; } diff --git a/platform/util/src/com/intellij/ui/IconManager.kt b/platform/util/src/com/intellij/ui/IconManager.kt index 626ab7f9204b..c926c57557fd 100644 --- a/platform/util/src/com/intellij/ui/IconManager.kt +++ b/platform/util/src/com/intellij/ui/IconManager.kt @@ -52,8 +52,11 @@ interface IconManager { fun getPlatformIcon(id: PlatformIcons): Icon + @Deprecated("Use getIcon(path, classLoader)") fun getIcon(path: String, aClass: Class<*>): Icon + fun getIcon(path: String, classLoader: ClassLoader): Icon + /** * Path must be specified without a leading slash, in a format for [ClassLoader.getResourceAsStream] */ @@ -98,6 +101,8 @@ private object DummyIconManager : IconManager { override fun getIcon(path: String, aClass: Class<*>): Icon = DummyIconImpl(path) + override fun getIcon(path: String, classLoader: ClassLoader): Icon = DummyIconImpl(path) + override fun loadRasterizedIcon(path: String, classLoader: ClassLoader, cacheKey: Int, flags: Int): Icon = DummyIconImpl(path) override fun createLayeredIcon(instance: Iconable, icon: Icon, flags: Int): RowIcon { diff --git a/platform/util/ui/src/com/intellij/openapi/util/IconLoader.kt b/platform/util/ui/src/com/intellij/openapi/util/IconLoader.kt index a24dfc3f8e20..5615fe692501 100644 --- a/platform/util/ui/src/com/intellij/openapi/util/IconLoader.kt +++ b/platform/util/ui/src/com/intellij/openapi/util/IconLoader.kt @@ -1,20 +1,17 @@ // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@file:Suppress("ReplaceGetOrSet", "ReplacePutWithAssignment", "DeprecatedCallableAddReplaceWith") +@file:Suppress("ReplaceGetOrSet", "ReplacePutWithAssignment", "DeprecatedCallableAddReplaceWith", "LiftReturnOrAssignment") package com.intellij.openapi.util +import com.github.benmanes.caffeine.cache.Caffeine import com.intellij.diagnostic.StartUpMeasurer import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.util.IconLoader.getReflectiveIcon import com.intellij.openapi.util.IconLoader.isReflectivePath -import com.intellij.openapi.util.IconLoader.patchPath import com.intellij.ui.* import com.intellij.ui.icons.* -import com.intellij.ui.icons.CachedImageIcon.Companion.pathTransform -import com.intellij.ui.icons.CachedImageIcon.Companion.pathTransformGlobalModCount -import com.intellij.ui.icons.ImageDataByPathLoader.Companion.findIcon import com.intellij.ui.paint.PaintUtil import com.intellij.ui.scale.DerivedScaleType import com.intellij.ui.scale.JBUIScale.sysScale @@ -25,7 +22,7 @@ import com.intellij.util.* import com.intellij.util.SVGLoader.SvgElementColorPatcherProvider import com.intellij.util.containers.CollectionFactory import com.intellij.util.ui.* -import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.ApiStatus.Internal import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.TestOnly import org.jetbrains.xxh3.Xxh3 @@ -38,6 +35,7 @@ import java.lang.ref.WeakReference import java.net.MalformedURLException import java.net.URL import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit import java.util.function.Function import java.util.function.Supplier import javax.swing.Icon @@ -50,7 +48,10 @@ private val LOG: Logger private val LOOKUP = MethodHandles.lookup() -private val iconCache = ConcurrentHashMap, CachedImageIcon>(100, 0.9f, 2) +private val iconCache = Caffeine.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .maximumSize(256) + .build, CachedImageIcon>() // contains mapping between icons and disabled icons private val iconToDisabledIcon = ConcurrentHashMap<() -> RGBImageFilter, MutableMap>() @@ -122,10 +123,10 @@ object IconLoader { @TestOnly @JvmStatic fun clearCacheInTests() { - iconCache.clear() + iconCache.invalidateAll() iconToDisabledIcon.clear() clearImageCache() - pathTransformGlobalModCount.incrementAndGet() + com.intellij.ui.icons.CachedImageIcon.pathTransformGlobalModCount.incrementAndGet() } @Deprecated("Use {@link #getIcon(String, ClassLoader)}", level = DeprecationLevel.ERROR) @@ -136,7 +137,7 @@ object IconLoader { @JvmStatic fun getReflectiveIcon(path: String, classLoader: ClassLoader): Icon? { - return try { + try { var dotIndex = path.lastIndexOf('.') val fieldName = path.substring(dotIndex + 1) val builder = StringBuilder(path.length + 20) @@ -161,11 +162,11 @@ object IconLoader { builder.insert(0, if (path.startsWith("AllIcons.")) "com.intellij.icons." else "icons.") } val aClass = classLoader.loadClass(builder.toString()) - LOOKUP.findStaticGetter(aClass, fieldName, Icon::class.java).invoke() as Icon + return LOOKUP.findStaticGetter(aClass, fieldName, Icon::class.java).invoke() as Icon } catch (e: Throwable) { LOG.warn("Cannot get reflective icon (path=$path)", e) - null + return null } } @@ -178,7 +179,7 @@ object IconLoader { @JvmStatic fun getIcon(path: String, aClass: Class<*>): Icon { - return findIcon(originalPath = path, aClass = aClass, classLoader = aClass.classLoader, handleNotFound = null, deferUrlResolve = true) + return findIcon(originalPath = path, aClass = aClass, classLoader = aClass.classLoader, deferUrlResolve = true) ?: throw IllegalStateException("Icon cannot be found in '$path', class='${aClass.name}'") } @@ -227,16 +228,12 @@ object IconLoader { */ @JvmStatic fun findIcon(path: String, aClass: Class<*>): Icon? { - return findIcon(originalPath = path, originalClassLoader = aClass.classLoader, cache = iconCache) + return findIconUsingNewImplementation(path, aClass.classLoader) } @JvmStatic fun findIcon(path: String, aClass: Class<*>, deferUrlResolve: Boolean, strict: Boolean): Icon? { - return findIcon(originalPath = path, - aClass = aClass, - classLoader = aClass.classLoader, - handleNotFound = if (strict) HandleNotFound.THROW_EXCEPTION else HandleNotFound.IGNORE, - deferUrlResolve = deferUrlResolve) + return findIcon(originalPath = path, aClass = aClass, classLoader = aClass.classLoader, strict, deferUrlResolve = deferUrlResolve) } fun isReflectivePath(path: String): Boolean { @@ -251,27 +248,22 @@ object IconLoader { } val key = Pair(url.toString(), null) - return if (storeToCache) { - iconCache.computeIfAbsent(key) { CachedImageIcon(url = url, useCacheOnLoad = true) } + if (storeToCache) { + return iconCache.get(key) { CachedImageIcon(url = url, useCacheOnLoad = true) } } else { - iconCache.get(key) ?: CachedImageIcon(url = url, useCacheOnLoad = false) + return iconCache.getIfPresent(key) ?: CachedImageIcon(url = url, useCacheOnLoad = false) } } - @JvmStatic - fun patchPath(originalPath: String, classLoader: ClassLoader): Pair? { - return pathTransform.get().patchPath(originalPath, classLoader) - } - @JvmStatic fun findIcon(path: String, classLoader: ClassLoader): Icon? { - return findIcon(originalPath = path, originalClassLoader = classLoader, cache = iconCache) + return findIconUsingNewImplementation(path, classLoader) } @JvmStatic fun findResolvedIcon(path: String, classLoader: ClassLoader): Icon? { - val icon = findIcon(originalPath = path, originalClassLoader = classLoader, cache = iconCache) + val icon = findIconUsingNewImplementation(path, classLoader) return if (icon is com.intellij.ui.icons.CachedImageIcon && icon.getRealIcon() === com.intellij.ui.icons.CachedImageIcon.EMPTY_ICON) null else icon } @@ -345,7 +337,7 @@ object IconLoader { return getDisabledIcon(icon = icon, disableFilter = null) } - @ApiStatus.Internal + @Internal fun getDisabledIcon(icon: Icon, disableFilter: (() -> RGBImageFilter)?): Icon { if (!com.intellij.ui.icons.CachedImageIcon.isActivated) { return icon @@ -365,7 +357,7 @@ object IconLoader { return replaceCachedImageIcons(icon) { patchColorsInCacheImageIcon(imageIcon = it, colorPatcher = colorPatcher, isDark = null) }!! } - @ApiStatus.Internal + @Internal fun patchColorsInCacheImageIcon(imageIcon: com.intellij.ui.icons.CachedImageIcon, colorPatcher: SvgElementColorPatcherProvider, isDark: Boolean?): Icon { var result = imageIcon if (isDark != null) { @@ -402,7 +394,7 @@ object IconLoader { /** * Creates a new icon with the low-level CachedImageIcon changing */ - @ApiStatus.Internal + @Internal fun replaceCachedImageIcons(icon: Icon, cachedImageIconReplacer: (com.intellij.ui.icons.CachedImageIcon) -> Icon): Icon? { val replacer: IconReplacer = object : IconReplacer { override fun replaceIcon(icon: Icon?): Icon? { @@ -530,7 +522,7 @@ object IconLoader { } fun detachClassLoader(classLoader: ClassLoader) { - iconCache.entries.removeIf { (key, icon): Map.Entry, com.intellij.ui.icons.CachedImageIcon> -> + iconCache.asMap().entries.removeIf { (key, icon) -> icon.detachClassLoader(classLoader) || key.second === classLoader } } @@ -585,11 +577,11 @@ private fun updateTransform(updater: Function) var prev: IconTransform var next: IconTransform do { - prev = pathTransform.get() + prev = CachedImageIcon.pathTransform.get() next = updater.apply(prev) } - while (!pathTransform.compareAndSet(prev, next)) - pathTransformGlobalModCount.incrementAndGet() + while (!CachedImageIcon.pathTransform.compareAndSet(prev, next)) + CachedImageIcon.pathTransformGlobalModCount.incrementAndGet() if (prev != next) { iconToDisabledIcon.clear() colorPatchCache.clear() @@ -604,15 +596,33 @@ private fun updateTransform(updater: Function) private fun findIcon(originalPath: String, aClass: Class<*>?, classLoader: ClassLoader, - handleNotFound: HandleNotFound?, + strict: Boolean = STRICT_LOCAL.get(), deferUrlResolve: Boolean): Icon? { - var effectiveClassLoader = classLoader - if (!deferUrlResolve) { - return findIcon(originalPath = originalPath, originalClassLoader = effectiveClassLoader, cache = iconCache) + if (deferUrlResolve) { + return findIconUsingDeprecatedImplementation(originalPath = originalPath, + classLoader = classLoader, + aClass = aClass, + strict = strict) } + else { + return findIconUsingNewImplementation(originalPath, classLoader) + } +} +@Internal +fun findIconUsingNewImplementation(path: String, classLoader: ClassLoader, toolTip: Supplier? = null): Icon? { + return ImageDataByPathLoader.findIconByPath(path = path, classLoader = classLoader, cache = iconCache.asMap(), toolTip = toolTip) +} + +@Internal +fun findIconUsingDeprecatedImplementation(originalPath: String, + classLoader: ClassLoader, + aClass: Class<*>?, + toolTip: Supplier? = null, + strict: Boolean = STRICT_LOCAL.get()): Icon? { + var effectiveClassLoader = classLoader val startTime = StartUpMeasurer.getCurrentTimeIfEnabled() - val patchedPath = patchPath(originalPath = originalPath, classLoader = effectiveClassLoader) + val patchedPath = CachedImageIcon.patchPath(originalPath = originalPath, classLoader = effectiveClassLoader) val path = patchedPath?.first ?: originalPath if (patchedPath?.second != null) { effectiveClassLoader = patchedPath.second @@ -624,16 +634,11 @@ private fun findIcon(originalPath: String, } else { val key = Pair(originalPath, effectiveClassLoader) - var cachedIcon = iconCache.get(key) + var cachedIcon = iconCache.getIfPresent(key) if (cachedIcon == null) { - cachedIcon = iconCache.computeIfAbsent(key) { k -> - val effectiveHandleNotFound = handleNotFound - ?: if (STRICT_LOCAL.get()) HandleNotFound.THROW_EXCEPTION else HandleNotFound.IGNORE - val resolver = ImageDataByPathResourceLoader(path = path, - ownerClass = aClass, - classLoader = k.second, - handleNotFound = effectiveHandleNotFound) - CachedImageIcon(originalPath = originalPath, resolver = resolver) + cachedIcon = iconCache.get(key) { k -> + val resolver = ImageDataByPathResourceLoader(path = path, ownerClass = aClass, classLoader = k.second, strict = strict) + CachedImageIcon(originalPath = originalPath, resolver = resolver, toolTip = toolTip) } } else { @@ -658,7 +663,7 @@ private abstract class LazyIcon : ScaleContextSupport(), CopyableIcon, Retrievab @Volatile private var icon: Icon? = null - private var transformModCount = pathTransformGlobalModCount.get() + private var transformModCount = CachedImageIcon.pathTransformGlobalModCount.get() override fun isComplex(): Boolean = true @@ -677,7 +682,7 @@ private abstract class LazyIcon : ScaleContextSupport(), CopyableIcon, Retrievab @Synchronized fun getOrComputeIcon(): Icon { var icon = icon - val newTransformModCount = pathTransformGlobalModCount.get() + val newTransformModCount = CachedImageIcon.pathTransformGlobalModCount.get() if (icon != null && wasComputed && transformModCount == newTransformModCount) { return icon } diff --git a/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt b/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt index 1de1e950183e..d2215f5b47f6 100644 --- a/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt +++ b/platform/util/ui/src/com/intellij/ui/icons/CachedImageIcon.kt @@ -25,6 +25,7 @@ import java.net.URL import java.util.concurrent.ConcurrentMap import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference +import java.util.function.Supplier import javax.swing.Icon import javax.swing.ImageIcon @@ -37,7 +38,9 @@ open class CachedImageIcon protected constructor( private val localFilterSupplier: (() -> RGBImageFilter)? = null, private val colorPatcher: SVGLoader.SvgElementColorPatcherProvider? = null, private val useStroke: Boolean = false, -) : AbstractScaleContextAware(ScaleContext.create()), CopyableIcon, ScalableIcon, DarkIconProvider, MenuBarIconProvider { + private val toolTip: Supplier? = null, +) : AbstractScaleContextAware(ScaleContext.create()), CopyableIcon, ScalableIcon, DarkIconProvider, MenuBarIconProvider, + IconWithToolTip { companion object { @JvmField internal var isActivated: Boolean = !GraphicsEnvironment.isHeadless() @@ -45,6 +48,10 @@ open class CachedImageIcon protected constructor( @JvmField internal val pathTransformGlobalModCount: AtomicInteger = AtomicInteger() + fun patchPath(originalPath: String, classLoader: ClassLoader): Pair? { + return pathTransform.get().patchPath(originalPath, classLoader) + } + @JvmField internal val pathTransform: AtomicReference = AtomicReference( IconTransform(StartupUiUtil.isUnderDarcula(), arrayOf(DeprecatedDuplicatesIconPathPatcher()), null) @@ -84,10 +91,8 @@ open class CachedImageIcon protected constructor( pathTransformModCount = pathTransformGlobalModCount.get() } - constructor(originalPath: String?, resolver: ImageDataLoader?) : this(originalPath = originalPath, - resolver = resolver, - isDarkOverridden = null, - colorPatcher = null) + constructor(originalPath: String?, resolver: ImageDataLoader?, toolTip: Supplier?) : + this(originalPath = originalPath, resolver = resolver, isDarkOverridden = null, colorPatcher = null, toolTip = toolTip) init { // for instance, ShadowPainter updates the context from and outside @@ -96,6 +101,8 @@ open class CachedImageIcon protected constructor( } } + override fun getToolTip(composite: Boolean): String? = toolTip?.get() + override fun paintIcon(c: Component?, g: Graphics, x: Int, y: Int) { getRealIcon(ScaleContext.create(if (g is Graphics2D) g else null)).paintIcon(c, g, x, y) } diff --git a/platform/core-ui/src/ui/icons/IconWithToolTip.java b/platform/util/ui/src/com/intellij/ui/icons/IconWithToolTip.java similarity index 84% rename from platform/core-ui/src/ui/icons/IconWithToolTip.java rename to platform/util/ui/src/com/intellij/ui/icons/IconWithToolTip.java index d6d653f0dbed..d59809699b0f 100644 --- a/platform/core-ui/src/ui/icons/IconWithToolTip.java +++ b/platform/util/ui/src/com/intellij/ui/icons/IconWithToolTip.java @@ -1,7 +1,6 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.ui.icons; -import com.intellij.openapi.util.NlsContexts; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -15,5 +14,5 @@ public interface IconWithToolTip extends Icon { * @param composite if true, this tooltip will be combined with other tooltips (from other layers of a layered icon or parts of a row icon). * For some icons, it only makes sense to show a tooltip if the icon is composite. */ - @NlsContexts.Tooltip @Nullable String getToolTip(boolean composite); + @Nullable String getToolTip(boolean composite); } diff --git a/platform/util/ui/src/com/intellij/ui/icons/ImageDataByPathLoader.kt b/platform/util/ui/src/com/intellij/ui/icons/ImageDataByPathLoader.kt index 34752c9720b2..52a77fe532b9 100644 --- a/platform/util/ui/src/com/intellij/ui/icons/ImageDataByPathLoader.kt +++ b/platform/util/ui/src/com/intellij/ui/icons/ImageDataByPathLoader.kt @@ -9,6 +9,7 @@ import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.NonNls import java.awt.Image import java.net.URL +import java.util.function.Supplier import javax.swing.Icon @ApiStatus.Internal @@ -16,31 +17,32 @@ class ImageDataByPathLoader private constructor(private val path: String, private val classLoader: ClassLoader, private val original: ImageDataByPathLoader?) : ImageDataLoader { companion object { - // cache is not used - image data resolved using cache in any case - fun findIcon(@NonNls originalPath: String, - originalClassLoader: ClassLoader, - cache: MutableMap, CachedImageIcon>?): Icon? { + fun findIconByPath(@NonNls path: String, + classLoader: ClassLoader, + cache: MutableMap, CachedImageIcon>?, + toolTip: Supplier? = null): Icon? { val startTime = StartUpMeasurer.getCurrentTimeIfEnabled() - @Suppress("NAME_SHADOWING") - val originalPath = normalizePath(originalPath) - val patched = IconLoader.patchPath(originalPath, originalClassLoader) - val path = patched?.first ?: originalPath - val classLoader = patched?.second ?: originalClassLoader + val originalPath = normalizePath(path) + val patched = CachedImageIcon.patchPath(originalPath, classLoader) + val effectivePath = patched?.first ?: originalPath + val effectiveClassLoader = patched?.second ?: classLoader val icon: Icon? = when { - IconLoader.isReflectivePath(path) -> IconLoader.getReflectiveIcon(path, classLoader) + IconLoader.isReflectivePath(effectivePath) -> IconLoader.getReflectiveIcon(effectivePath, effectiveClassLoader) cache == null -> createIcon(originalPath = originalPath, - originalClassLoader = originalClassLoader, + originalClassLoader = effectiveClassLoader, patched = patched, - path = path, - classLoader = classLoader) + path = effectivePath, + classLoader = effectiveClassLoader, + toolTip = toolTip) else -> { - cache.computeIfAbsent(Pair(originalPath, originalClassLoader)) { + cache.computeIfAbsent(Pair(originalPath, effectiveClassLoader)) { createIcon(originalPath = it.first, originalClassLoader = it.second!!, patched = patched, - path = path, - classLoader = classLoader) + path = effectivePath, + classLoader = effectiveClassLoader, + toolTip = toolTip) } } } @@ -54,10 +56,11 @@ class ImageDataByPathLoader private constructor(private val path: String, originalClassLoader: ClassLoader, patched: Pair?, path: String, - classLoader: ClassLoader): CachedImageIcon { - val loader = ImageDataByPathLoader(originalPath, originalClassLoader, null) - val resolver = if (patched == null) loader else ImageDataByPathLoader(path, classLoader, loader) - return CachedImageIcon(originalPath = null, resolver = resolver) + classLoader: ClassLoader, + toolTip: Supplier? = null): CachedImageIcon { + val loader = ImageDataByPathLoader(path = originalPath, classLoader = originalClassLoader, original = null) + val resolver = if (patched == null) loader else ImageDataByPathLoader(path = path, classLoader = classLoader, original = loader) + return CachedImageIcon(originalPath = null, resolver = resolver, toolTip = toolTip) } private fun normalizePath(patchedPath: String): String { diff --git a/platform/util/ui/src/com/intellij/ui/icons/ImageDataByUrlLoader.kt b/platform/util/ui/src/com/intellij/ui/icons/ImageDataByUrlLoader.kt index 418821cabffa..eca616e43c64 100644 --- a/platform/util/ui/src/com/intellij/ui/icons/ImageDataByUrlLoader.kt +++ b/platform/util/ui/src/com/intellij/ui/icons/ImageDataByUrlLoader.kt @@ -11,18 +11,6 @@ import java.net.URL private val UNRESOLVED_URL = URL("file:///unresolved") -internal enum class HandleNotFound { - THROW_EXCEPTION { - override fun handle(message: String) { - throw RuntimeException(message) - } - }, - IGNORE; - - open fun handle(message: String) { - } -} - @ApiStatus.Internal class ImageDataByUrlLoader private constructor( private val ownerClass: Class<*>? = null, @@ -65,7 +53,7 @@ class ImageDataByUrlLoader private constructor( internal class ImageDataByPathResourceLoader( private val ownerClass: Class<*>? = null, private val classLoader: ClassLoader? = null, - private val handleNotFound: HandleNotFound, + private val strict: Boolean, private val path: String, ) : ImageDataLoader { @Volatile @@ -74,7 +62,7 @@ internal class ImageDataByPathResourceLoader( var result = field if (result === UNRESOLVED_URL) { result = try { - resolveUrl(path = path, classLoader = classLoader, ownerClass = ownerClass, handleNotFound = handleNotFound) + resolveUrl(path = path, classLoader = classLoader, ownerClass = ownerClass, strict = strict) } finally { field = result @@ -106,7 +94,7 @@ internal class ImageDataByPathResourceLoader( private fun resolveUrl(path: String?, classLoader: ClassLoader?, ownerClass: Class<*>?, - handleNotFound: HandleNotFound): URL? { + strict: Boolean): URL? { var effectivePath = path var url: URL? = null if (effectivePath != null) { @@ -120,8 +108,8 @@ private fun resolveUrl(path: String?, url = findUrl(path = effectivePath, urlProvider = ownerClass::getResource) } } - if (url == null) { - handleNotFound.handle("Can't find icon in '$effectivePath' near $classLoader") + if (url == null && strict) { + throw RuntimeException("Can't find icon in '$effectivePath' near $classLoader") } return url }