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 2b9eb166f91a..0f21fd594fa4 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,6 +4,7 @@ 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 @@ -151,33 +152,39 @@ class CustomActionsSchema(private val coroutineScope: CoroutineScope?) : Persist */ @ApiStatus.Internal @Throws(Throwable::class) - @JvmStatic fun loadCustomIcon(path: String): Icon { val independentPath = FileUtil.toSystemIndependentName(path) val urlString = if (independentPath.startsWith("file:") || independentPath.startsWith("jar:")) { independentPath } - else "file:$independentPath" + else { + "file:$independentPath" + } val lastDotIndex = urlString.lastIndexOf('.') - val (rawUrl, ext) = if (lastDotIndex != -1) { + val (rawUrl, ext) = if (lastDotIndex == -1) { + urlString to "svg" + } + else { urlString.substring(0, lastDotIndex) to urlString.substring(lastDotIndex + 1) } - else urlString to "svg" + 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" - return try { - doLoadCustomIcon(fullAdjustedUrl) + 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) { - doLoadCustomIcon(urlString) + if (urlString == fullAdjustedUrl) { + throw t + } + else { + return doLoadCustomIcon(urlString) } - else throw t } } @@ -189,11 +196,12 @@ class CustomActionsSchema(private val coroutineScope: CoroutineScope?) : Persist 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 s = EmptyIcon.ICON_18.iconWidth / w.coerceAtLeast(h).toFloat() + 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 = s, ancestor = null) + return IconUtil.scale(icon, scale = scale, ancestor = null) } return icon } @@ -498,7 +506,7 @@ class CustomActionsSchema(private val coroutineScope: CoroutineScope?) : Persist presentation.putClientProperty(PROP_ORIGINAL_ICON, originalIcon) } - val icon = iconCustomizations.get(actionId)?.let { CustomizationUtil.getIconForPath(actionManager, it) } + val icon = iconCustomizations.get(actionId)?.let { getIconForPath(actionManager = actionManager, iconPath = it) } ?: presentation.getClientProperty(PROP_ORIGINAL_ICON) presentation.icon = icon presentation.disabledIcon = if (icon == null) null else getDisabledIcon(icon) @@ -553,4 +561,25 @@ private object ActionUrlComparator : Comparator { return u1.absolutePosition - u2.absolutePosition } } +} + +internal fun getIconForPath(actionManager: ActionManager, iconPath: String): Icon? { + val reuseFrom = actionManager.getAction(iconPath) + if (reuseFrom != null) { + return getOriginalIconFrom(reuseFrom) + } + else { + try { + return loadCustomIcon(iconPath) + } + catch (e: Throwable) { + LOG.info(e.message) + return null + } + } +} + +internal fun getOriginalIconFrom(reuseFrom: AnAction): Icon? { + val presentation = reuseFrom.templatePresentation + return presentation.getClientProperty(CustomActionsSchema.PROP_ORIGINAL_ICON) ?: presentation.icon } \ No newline at end of file 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 7c12226f8a85..97d1269356ce 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 @@ -342,16 +342,16 @@ public class CustomizableActionsPanel { if (userObj instanceof String actionId) { AnAction action = ActionManager.getInstance().getAction(actionId); if (action != null) { - return Pair.create(actionId, action.getTemplatePresentation().getIcon()); + return new Pair<>(actionId, action.getTemplatePresentation().getIcon()); } } else if (userObj instanceof Group group) { - return Pair.create(group.getId(), group.getIcon()); + return new Pair<>(group.getId(), group.getIcon()); } else if (userObj instanceof Pair pair) { Object first = pair.first; String actionId = first instanceof Group group ? group.getId() : (String)first; - return Pair.create(actionId, (Icon)pair.second); + return new Pair<>(actionId, (Icon)pair.second); } return Pair.empty(); } @@ -392,15 +392,15 @@ public class CustomizableActionsPanel { AnAction reuseFrom = actionManager.getAction(path); if (reuseFrom != null) { - Icon toSet = CustomizationUtil.getOriginalIconFrom(reuseFrom); - Icon defaultIcon = CustomizationUtil.getOriginalIconFrom(action); + Icon toSet = CustomActionsSchemaKt.getOriginalIconFrom(reuseFrom); + Icon defaultIcon = CustomActionsSchemaKt.getOriginalIconFrom(action); node.setUserObject(Pair.create(value, toSet)); schema.addIconCustomization(actionId, toSet != defaultIcon ? path : null); } else { Icon icon; try { - icon = CustomActionsSchema.loadCustomIcon(path); + icon = CustomActionsSchema.Companion.loadCustomIcon(path); } catch (Throwable t) { Logger.getInstance(CustomizableActionsPanel.class) @@ -530,8 +530,8 @@ public class CustomizableActionsPanel { if (path.isEmpty()) { path = actionId; } - Icon icon = CustomizationUtil.getIconForPath(ActionManager.getInstance(), path); - newNode.setUserObject(Pair.create(actionId, icon)); + Icon icon = CustomActionsSchemaKt.getIconForPath(ActionManager.getInstance(), path); + newNode.setUserObject(new Pair<>(actionId, icon)); } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomizationUtil.java b/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomizationUtil.java index 1eab237c6e3e..1ab06bcb6459 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomizationUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/customization/CustomizationUtil.java @@ -64,38 +64,10 @@ public final class CustomizationUtil { private CustomizationUtil() { } - public static @Nullable Icon getOriginalIconFrom(@NotNull AnAction reuseFrom) { - Presentation presentation = reuseFrom.getTemplatePresentation(); - Icon original = presentation.getClientProperty(CustomActionsSchema.PROP_ORIGINAL_ICON); - if (original != null) return original; - return presentation.getIcon(); - } - - public static @Nullable Icon getIconForPath(@NotNull ActionManager actionManager, @Nullable String iconPath) { - if (iconPath == null) { - return null; - } - AnAction reuseFrom = actionManager.getAction(iconPath); - if (reuseFrom != null) { - return getOriginalIconFrom(reuseFrom); - } - else { - try { - return CustomActionsSchema.loadCustomIcon(iconPath); - } - catch (Throwable t) { - LOG.info(t.getMessage()); - return null; - } - } - } - - - - public static ActionGroup correctActionGroup(final ActionGroup group, - final CustomActionsSchema schema, - final String defaultGroupName, - final String rootGroupName, + public static ActionGroup correctActionGroup(ActionGroup group, + CustomActionsSchema schema, + String defaultGroupName, + String rootGroupName, boolean force) { if (!force && !schema.isCorrectActionGroup(group, defaultGroupName)) { return group; diff --git a/platform/platform-impl/src/com/intellij/ui/PathChooserDialogHelper.kt b/platform/platform-impl/src/com/intellij/ui/PathChooserDialogHelper.kt index 0596ac676f8a..7e15adecfc40 100644 --- a/platform/platform-impl/src/com/intellij/ui/PathChooserDialogHelper.kt +++ b/platform/platform-impl/src/com/intellij/ui/PathChooserDialogHelper.kt @@ -12,7 +12,6 @@ import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileSystem import com.intellij.openapi.vfs.local.CoreLocalFileSystem -import com.intellij.util.SmartList import java.io.File internal class PathChooserDialogHelper(private val descriptor: FileChooserDescriptor) { @@ -32,7 +31,7 @@ internal class PathChooserDialogHelper(private val descriptor: FileChooserDescri } fun getChosenFiles(files: Array): List { - val virtualFiles = files.mapNotNullTo(SmartList()) { + val virtualFiles = files.mapNotNull { val virtualFile = fileToVirtualFile(it) if (virtualFile != null && virtualFile.isValid) { virtualFile @@ -49,12 +48,10 @@ internal class PathChooserDialogHelper(private val descriptor: FileChooserDescri } companion object { - @JvmStatic private fun fileToVirtualFile(fileSystem: VirtualFileSystem, file: File): VirtualFile? { return fileSystem.refreshAndFindFileByPath(FileUtilRt.toSystemIndependentName(file.absolutePath)) } - @JvmStatic fun fileToCoreLocalVirtualFile(dir: File, name: String): VirtualFile? { return fileToVirtualFile(CoreLocalFileSystem(), File(dir, name)) } diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacFileSaverDialog.java b/platform/platform-impl/src/com/intellij/ui/mac/MacFileSaverDialog.java index f7c97b8901b2..4b5f160080d4 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/MacFileSaverDialog.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacFileSaverDialog.java @@ -1,4 +1,4 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// 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.mac; import com.intellij.openapi.fileChooser.FileChooser; @@ -11,13 +11,13 @@ import com.intellij.openapi.vfs.VirtualFileWrapper; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.PathChooserDialogHelper; import com.intellij.ui.UIBundle; -import com.intellij.util.Consumer; import com.intellij.util.ui.OwnerOptional; import org.jetbrains.annotations.Nullable; import java.awt.*; import java.io.File; import java.nio.file.Path; +import java.util.function.Consumer; public final class MacFileSaverDialog implements FileSaverDialog { private FileDialog myFileDialog; @@ -60,7 +60,7 @@ public final class MacFileSaverDialog implements FileSaverDialog { myFileDialog.setDirectory(baseDir); myFileDialog.setFile(filename); myFileDialog.setFilenameFilter(FileChooser.safeInvokeFilter((dir, name) -> { - return myDescriptor.isFileSelectable(PathChooserDialogHelper.fileToCoreLocalVirtualFile(dir, name)); + return myDescriptor.isFileSelectable(PathChooserDialogHelper.Companion.fileToCoreLocalVirtualFile(dir, name)); }, false)); myFileDialog.setVisible(true); diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacPathChooserDialog.java b/platform/platform-impl/src/com/intellij/ui/mac/MacPathChooserDialog.java index b8452d686fe9..bb33a03442f1 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/MacPathChooserDialog.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacPathChooserDialog.java @@ -95,7 +95,7 @@ public final class MacPathChooserDialog implements PathChooserDialog, FileChoose myFileDialog.setFilenameFilter(FileChooser.safeInvokeFilter((dir, name) -> { - return myFileChooserDescriptor.isFileSelectable(PathChooserDialogHelper.fileToCoreLocalVirtualFile(dir, name)); + return myFileChooserDescriptor.isFileSelectable(PathChooserDialogHelper.Companion.fileToCoreLocalVirtualFile(dir, name)); }, false)); myFileDialog.setMultipleMode(myFileChooserDescriptor.isChooseMultiple()); diff --git a/platform/platform-impl/src/com/intellij/util/ui/OwnerOptional.java b/platform/platform-impl/src/com/intellij/util/ui/OwnerOptional.java index 0260f8169d69..18bf5df061a4 100644 --- a/platform/platform-impl/src/com/intellij/util/ui/OwnerOptional.java +++ b/platform/platform-impl/src/com/intellij/util/ui/OwnerOptional.java @@ -1,4 +1,4 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// 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.util.ui; import com.intellij.ide.IdeEventQueue; @@ -6,10 +6,10 @@ import com.intellij.ide.IdePopupManager; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.WindowManager; -import com.intellij.util.Consumer; import javax.swing.*; import java.awt.*; +import java.util.function.Consumer; public final class OwnerOptional { private static Window findOwnerByComponent(Component component) { @@ -63,21 +63,21 @@ public final class OwnerOptional { public OwnerOptional ifDialog(Consumer consumer) { if (myPermanentOwner instanceof Dialog) { - consumer.consume((Dialog)myPermanentOwner); + consumer.accept((Dialog)myPermanentOwner); } return this; } public OwnerOptional ifNull(Consumer consumer) { if (myPermanentOwner == null) { - consumer.consume(null); + consumer.accept(null); } return this; } public OwnerOptional ifWindow(Consumer consumer) { if (myPermanentOwner != null) { - consumer.consume(myPermanentOwner); + consumer.accept(myPermanentOwner); } return this; } @@ -87,7 +87,7 @@ public final class OwnerOptional { if (myPermanentOwner instanceof IdeFrame.Child ideFrameChild) { myPermanentOwner = WindowManager.getInstance().getFrame(ideFrameChild.getProject()); } - consumer.consume((Frame)this.myPermanentOwner); + consumer.accept((Frame)this.myPermanentOwner); } return this; } 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 f684eebd85db..1811b31def08 100644 --- a/platform/util/ui/src/com/intellij/ui/icons/ImageDataByUrlLoader.kt +++ b/platform/util/ui/src/com/intellij/ui/icons/ImageDataByUrlLoader.kt @@ -39,7 +39,7 @@ internal class ImageDataByUrlLoader internal constructor( override fun isMyClassLoader(classLoader: ClassLoader): Boolean = this.classLoader === classLoader override fun toString(): String { - return "UrlResolver(ownerClass=${ownerClass?.name}, classLoader=$classLoader, url=$url, useCacheOnLoad=$useCacheOnLoad)" + return "ImageDataByUrlLoader(ownerClass=${ownerClass?.name}, classLoader=$classLoader, url=$url, useCacheOnLoad=$useCacheOnLoad)" } } @@ -81,7 +81,7 @@ internal class ImageDataByPathResourceLoader( override fun isMyClassLoader(classLoader: ClassLoader): Boolean = this.classLoader === classLoader - override fun toString(): String = "UrlResolver(ownerClass=${ownerClass?.name}, classLoader=$classLoader, path=$path)" + override fun toString(): String = "ImageDataByPathResourceLoader(ownerClass=${ownerClass?.name}, classLoader=$classLoader, path=$path)" } private fun resolveUrl(path: String?,