From e0580de9af00e41d983b0ccb593e613a36d00179 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Tue, 17 Oct 2023 21:17:06 +0200 Subject: [PATCH] IDEA-335157 cleanup GitOrigin-RevId: b6887791c62244681633a9ce3b0f0ef9b25c3c72 --- .../ide/ui/customization/ActionIconInfo.kt | 4 +- .../ui/customization/BrowseIconsComboBox.kt | 12 +- .../ui/customization/CustomActionsSchema.kt | 123 +++++++++--------- .../CustomizableActionsPanel.java | 2 +- .../com/intellij/ui/CustomIconLoadingTest.kt | 6 +- 5 files changed, 73 insertions(+), 74 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ide/ui/customization/ActionIconInfo.kt b/platform/platform-impl/src/com/intellij/ide/ui/customization/ActionIconInfo.kt index bc96c41eafc9..935a17840807 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/customization/ActionIconInfo.kt +++ b/platform/platform-impl/src/com/intellij/ide/ui/customization/ActionIconInfo.kt @@ -77,8 +77,8 @@ internal fun getCustomIcons(schema: CustomActionsSchema): List { val action = actionManager.getAction(iconReference) if (action == null) { try { - val icon = CustomActionsSchema.loadCustomIcon(iconReference) - ActionIconInfo(icon, iconReference.substringAfterLast("/"), actionId, iconReference) + val icon = loadCustomIcon(iconReference) + ActionIconInfo(icon = icon, text = iconReference.substringAfterLast('/'), actionId = actionId, iconPath = iconReference) } catch (ex: IOException) { null diff --git a/platform/platform-impl/src/com/intellij/ide/ui/customization/BrowseIconsComboBox.kt b/platform/platform-impl/src/com/intellij/ide/ui/customization/BrowseIconsComboBox.kt index 985d1f5500de..71959f6ad98a 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/customization/BrowseIconsComboBox.kt +++ b/platform/platform-impl/src/com/intellij/ide/ui/customization/BrowseIconsComboBox.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// 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.ide.ui.customization import com.intellij.icons.AllIcons @@ -47,7 +47,7 @@ internal class BrowseIconsComboBox(private val customActionsSchema: CustomAction init { iconsLoadedFuture = loadIconsAsync(withNoneItem) - isSwingPopup = false // in this case speed search will filter the list of items + isSwingPopup = false // in this case, speed search will filter the list of items setEditable(true) setEditor(createEditor()) setRenderer(createRenderer()) @@ -57,7 +57,7 @@ internal class BrowseIconsComboBox(private val customActionsSchema: CustomAction private fun loadIconsAsync(withNoneItem: Boolean): CompletableFuture { val future = CompletableFuture() - ReadAction.nonBlocking(Callable { createIconsList(withNoneItem) }) + ReadAction.nonBlocking(Callable { createIconList(withNoneItem) }) .expireWith(parentDisposable) .finishOnUiThread(ModalityState.any(), Consumer { icons -> model = DefaultComboBoxModel(icons.toTypedArray()) @@ -67,7 +67,7 @@ internal class BrowseIconsComboBox(private val customActionsSchema: CustomAction return future } - private fun createIconsList(withNoneItem: Boolean): List { + private fun createIconList(withNoneItem: Boolean): List { val defaultIcons = getDefaultIcons() val customIcons = getCustomIcons(customActionsSchema) .asSequence() @@ -161,7 +161,7 @@ internal class BrowseIconsComboBox(private val customActionsSchema: CustomAction ComponentValidator(parentDisposable).withValidator(Supplier { val path = (selectedItem as? ActionIconInfo)?.iconPath ?: return@Supplier null try { - CustomActionsSchema.loadCustomIcon(path) + loadCustomIcon(path) null } catch (ex: FileNotFoundException) { @@ -204,7 +204,7 @@ internal class BrowseIconsComboBox(private val customActionsSchema: CustomAction val iconFile = FileChooser.chooseFile(descriptor, null, null) if (iconFile != null) { val icon = try { - CustomActionsSchema.loadCustomIcon(iconFile.path) + loadCustomIcon(iconFile.path) } catch (t: Throwable) { thisLogger().warn("Failed to load icon from disk, path: ${iconFile.path}", t) diff --git a/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomActionsSchema.kt b/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomActionsSchema.kt index 0f21fd594fa4..d2457f24803c 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomActionsSchema.kt +++ b/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomActionsSchema.kt @@ -4,7 +4,6 @@ package com.intellij.ide.ui.customization import com.intellij.ide.IdeBundle -import com.intellij.ide.ui.customization.CustomActionsSchema.Companion.loadCustomIcon import com.intellij.ide.ui.customization.CustomizableActionGroupProvider.CustomizableActionGroupRegistrar import com.intellij.openapi.actionSystem.* import com.intellij.openapi.actionSystem.impl.PresentationFactory @@ -144,67 +143,6 @@ class CustomActionsSchema(private val coroutineScope: CoroutineScope?) : Persist } windowManager.getFrameHelper(null)?.updateView() } - - /** - * @param path absolute path to the icon file, url of the icon file or url of the icon file inside jar. - * Also, the path can contain '_dark', '@2x', '@2x_dark' suffixes, but the resulting icon will be taken - * according to current scale and UI theme. - */ - @ApiStatus.Internal - @Throws(Throwable::class) - fun loadCustomIcon(path: String): Icon { - val independentPath = FileUtil.toSystemIndependentName(path) - val urlString = if (independentPath.startsWith("file:") || independentPath.startsWith("jar:")) { - independentPath - } - else { - "file:$independentPath" - } - - val lastDotIndex = urlString.lastIndexOf('.') - val (rawUrl, ext) = if (lastDotIndex == -1) { - urlString to "svg" - } - else { - urlString.substring(0, lastDotIndex) to urlString.substring(lastDotIndex + 1) - } - - val possibleSuffixes = listOf("@2x_dark", "_dark@2x", "_dark", "@2x") - val adjustedUrl = possibleSuffixes.find { rawUrl.endsWith(it) }?.let { rawUrl.removeSuffix(it) } ?: rawUrl - val fullAdjustedUrl = "$adjustedUrl.$ext" - try { - return doLoadCustomIcon(fullAdjustedUrl) - } - catch (t: Throwable) { - // In Light theme we do not fall back on dark icon, so if the original provided path ends with '_dark' - // and there is no icon file without '_dark' suffix, we will fail. - // And in this case, we just need to load the file chosen by the user. - if (urlString == fullAdjustedUrl) { - throw t - } - else { - return doLoadCustomIcon(urlString) - } - } - } - - private fun doLoadCustomIcon(urlString: String): Icon { - val url = URL(null, urlString) - val icon = IconLoader.findIcon(url) ?: throw FileNotFoundException("Failed to find icon by URL: $url") - val w = icon.iconWidth - val h = icon.iconHeight - if (w <= 1 || h <= 1) { - throw FileNotFoundException("Failed to find icon by URL: $url") - } - - if (w > EmptyIcon.ICON_18.iconWidth || h > EmptyIcon.ICON_18.iconHeight) { - val scale = EmptyIcon.ICON_18.iconWidth / w.coerceAtLeast(h).toFloat() - // ScaledResultIcon will be returned here, so we will be unable to scale it again or get the dark version, - // but we have nothing to do because the icon is too large - return IconUtil.scale(icon, scale = scale, ancestor = null) - } - return icon - } } fun addAction(url: ActionUrl) { @@ -563,6 +501,67 @@ private object ActionUrlComparator : Comparator { } } +/** + * @param path absolute path to the icon file, url of the icon file or url of the icon file inside jar. + * Also, the path can contain '_dark', '@2x', '@2x_dark' suffixes, but the resulting icon will be taken + * according to current scale and UI theme. + */ +@ApiStatus.Internal +@Throws(Throwable::class) +fun loadCustomIcon(path: String): Icon { + val independentPath = FileUtil.toSystemIndependentName(path) + val urlString = if (independentPath.startsWith("file:") || independentPath.startsWith("jar:")) { + independentPath + } + else { + "file:$independentPath" + } + + val lastDotIndex = urlString.lastIndexOf('.') + val (rawUrl, ext) = if (lastDotIndex == -1) { + urlString to "svg" + } + else { + urlString.substring(0, lastDotIndex) to urlString.substring(lastDotIndex + 1) + } + + val possibleSuffixes = listOf("@2x_dark", "_dark@2x", "_dark", "@2x") + val adjustedUrl = possibleSuffixes.find { rawUrl.endsWith(it) }?.let { rawUrl.removeSuffix(it) } ?: rawUrl + val fullAdjustedUrl = "$adjustedUrl.$ext" + try { + return doLoadCustomIcon(fullAdjustedUrl) + } + catch (t: Throwable) { + // In Light theme we do not fall back on dark icon, so if the original provided path ends with '_dark' + // and there is no icon file without '_dark' suffix, we will fail. + // And in this case, we just need to load the file chosen by the user. + if (urlString == fullAdjustedUrl) { + throw t + } + else { + return doLoadCustomIcon(urlString) + } + } +} + +private fun doLoadCustomIcon(urlString: String): Icon { + val url = URL(null, urlString) + val icon = IconLoader.findIcon(url) ?: throw FileNotFoundException("Failed to find icon by URL: $url") + val w = icon.iconWidth + val h = icon.iconHeight + if (w <= 1 || h <= 1) { + throw FileNotFoundException("Failed to find icon by URL: $url") + } + + if (w > EmptyIcon.ICON_18.iconWidth || h > EmptyIcon.ICON_18.iconHeight) { + val scale = EmptyIcon.ICON_18.iconWidth / w.coerceAtLeast(h).toFloat() + // ScaledResultIcon will be returned here, so we will be unable to scale it again or get the dark version, + // but we have nothing to do because the icon is too large + return IconUtil.scale(icon, scale = scale, ancestor = null) + } + return icon +} + internal fun getIconForPath(actionManager: ActionManager, iconPath: String): Icon? { val reuseFrom = actionManager.getAction(iconPath) if (reuseFrom != null) { diff --git a/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomizableActionsPanel.java b/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomizableActionsPanel.java index 97d1269356ce..dd2653db1729 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomizableActionsPanel.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomizableActionsPanel.java @@ -400,7 +400,7 @@ public class CustomizableActionsPanel { else { Icon icon; try { - icon = CustomActionsSchema.Companion.loadCustomIcon(path); + icon = CustomActionsSchemaKt.loadCustomIcon(path); } catch (Throwable t) { Logger.getInstance(CustomizableActionsPanel.class) diff --git a/platform/platform-tests/testSrc/com/intellij/ui/CustomIconLoadingTest.kt b/platform/platform-tests/testSrc/com/intellij/ui/CustomIconLoadingTest.kt index 1b634eacbe91..805aba971baa 100644 --- a/platform/platform-tests/testSrc/com/intellij/ui/CustomIconLoadingTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ui/CustomIconLoadingTest.kt @@ -1,7 +1,7 @@ -// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// 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 -import com.intellij.ide.ui.customization.CustomActionsSchema +import com.intellij.ide.ui.customization.loadCustomIcon import com.intellij.openapi.util.IconLoader import com.intellij.testFramework.PlatformTestUtil import org.junit.Test @@ -42,7 +42,7 @@ class CustomIconLoadingTest { private fun doTest(path: String) { IconLoader.activate() try { - CustomActionsSchema.loadCustomIcon(path) + loadCustomIcon(path) } finally { IconLoader.deactivate()