From cd39f261ddb39994f972f1b2e88f4a53a2e375e4 Mon Sep 17 00:00:00 2001 From: Ekaterina Chernikova Date: Mon, 3 Mar 2025 11:50:01 +0100 Subject: [PATCH] DBE-15813: Converted ErrorNotificationPanel.java to Kotlin GitOrigin-RevId: 11da616e78b6bdf749c5e376b503d7bbbbf0b3d3 --- .../src/run/ui/ErrorNotificationPanel.java | 301 ------------------ .../impl/src/run/ui/ErrorNotificationPanel.kt | 296 +++++++++++++++++ 2 files changed, 296 insertions(+), 301 deletions(-) delete mode 100644 grid/impl/src/run/ui/ErrorNotificationPanel.java create mode 100644 grid/impl/src/run/ui/ErrorNotificationPanel.kt diff --git a/grid/impl/src/run/ui/ErrorNotificationPanel.java b/grid/impl/src/run/ui/ErrorNotificationPanel.java deleted file mode 100644 index f56e5a64c65c..000000000000 --- a/grid/impl/src/run/ui/ErrorNotificationPanel.java +++ /dev/null @@ -1,301 +0,0 @@ -package com.intellij.database.run.ui; - -import com.intellij.CommonBundle; -import com.intellij.database.DataGridBundle; -import com.intellij.database.datagrid.GridUtil; -import com.intellij.icons.AllIcons; -import com.intellij.ide.CopyProvider; -import com.intellij.ide.IdeTooltipManager; -import com.intellij.ide.TextCopyProvider; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.HintHint; -import com.intellij.ui.HyperlinkAdapter; -import com.intellij.util.Consumer; -import com.intellij.util.ExceptionUtil; -import com.intellij.util.ObjectUtils; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.JBInsets; -import com.intellij.util.ui.JBUI; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; -import java.awt.*; -import java.awt.event.HierarchyEvent; -import java.awt.event.HierarchyListener; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.util.List; -import java.util.*; - -public final class ErrorNotificationPanel extends JPanel { - - private final JEditorPane myMessagePane; - private final Map myActions; - private final CopyProvider myCopyProvider; - private final MessageType myType; - - private ErrorNotificationPanel(@NlsContexts.NotificationContent @NotNull String htmlErrorMessage, - @NotNull Map actions, - @NotNull MessageType type) { - super(new BorderLayout()); - myActions = actions; - myType = type; - - setBorder(JBUI.Borders.empty(0, 4)); - - myMessagePane = IdeTooltipManager.initPane(htmlErrorMessage, new HintHint() - .setAwtTooltip(false) - .setTextFg(getForeground()) - .setTextBg(getBackground()) - .setBorderColor(getBackground()) - .setBorderInsets(JBInsets.emptyInsets()), null); - myMessagePane.setBorder(null); - myMessagePane.addHyperlinkListener(new HyperlinkAdapter() { - @Override - protected void hyperlinkActivated(@NotNull HyperlinkEvent e) { - performAction(e.getDescription()); - } - }); - myCopyProvider = new TextCopyProvider() { - @Override - public @NotNull ActionUpdateThread getActionUpdateThread() { - return ActionUpdateThread.EDT; - } - - @Override - public @Nullable Collection getTextLinesToCopy() { - String text = myMessagePane.getSelectedText(); - return StringUtil.isEmpty(text) ? null : Collections.singleton(text); - } - }; - add(UiDataProvider.wrapComponent(myMessagePane, sink -> { - sink.set(PlatformDataKeys.COPY_PROVIDER, this.myCopyProvider); - }), BorderLayout.CENTER); - - new DumbAwareAction(DataGridBundle.message("action.close.text")) { - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - performAction(DataGridBundle.message("action.close.text")); - } - }.registerCustomShortcutSet(new CustomShortcutSet(KeyEvent.VK_ESCAPE), this); - } - - private void performAction(String actionName) { - Runnable action = myActions.get(actionName); - if (action != null) { - action.run(); - } - } - - @Override - public @NotNull Color getBackground() { - return getType().getPopupBackground(); - } - - @Override - public @NotNull Color getForeground() { - return getType().getTitleForeground(); - } - - private @NotNull MessageType getType() { - return ObjectUtils.chooseNotNull(myType, MessageType.ERROR); - } - - @Override - public @NotNull Dimension getMinimumSize() { - return JBUI.emptySize(); - } - - public JComponent getContent() { - return myMessagePane; - } - - public static @NotNull Builder create(@Nullable @NlsContexts.NotificationContent String message, @Nullable Throwable error, @NotNull JComponent baseComponent) { - return new Builder(message, error, baseComponent); - } - - public static final class Builder { - private final boolean myLongMessage; - private final Throwable myError; - private final @NlsContexts.NotificationContent String myMessage; - private final JComponent myBaseComponent; - - private final Map myActions = new LinkedHashMap<>(); - private final List> myShowHideHandlers = new ArrayList<>(); - private final StringBuilder myHtmlBuilder = new StringBuilder(); - - private boolean isChoppedMessage = false; - - private MessageType myType = MessageType.ERROR; - - private Builder(@NlsContexts.NotificationContent @Nullable String message, @Nullable Throwable error, @NotNull JComponent baseComponent) { - myError = error; - myMessage = message; - myBaseComponent = baseComponent; - - String errorMessage = message == null ? error == null ? null : getNormalizedMessage(error) : getNormalized(message); - Font font = IdeTooltipManager.getInstance().getTextFont(true); - FontMetrics fm = baseComponent.getFontMetrics(font); - myLongMessage = SwingUtilities.computeStringWidth(fm, errorMessage) > baseComponent.getWidth() * 3 / 4; - if (errorMessage != null) { - errorMessage = StringUtil.escapeXmlEntities(errorMessage).replace("\n", "
"); - } - - myHtmlBuilder.append(""); - myHtmlBuilder.append(""); - } - - public @NotNull Builder messageType(@NotNull MessageType type) { - myType = type; - return this; - } - - public @NotNull Builder addIconLink(String command, @NlsContexts.Tooltip String tooltipText, @NotNull Icon realIcon, @Nullable Runnable action) { - String iconPath = GridUtil.getIconPath(realIcon); - - startActionColumn(); - myHtmlBuilder.append(""); - endActionColumn(); - - if (action != null) { - myActions.put(command, action); - } - - return this; - } - - public @NotNull Builder addSpace() { - startActionColumn(); - endActionColumn(); - return this; - } - - public @NotNull Builder addLink(@NonNls @NotNull String command, @NlsActions.ActionText @NotNull String linkHtml, @NotNull Runnable action) { - startActionColumn(); - int mnemonicIndex = UIUtil.getDisplayMnemonicIndex(command); - @NlsSafe - String fixedCommand = mnemonicIndex < 0 ? command : command.substring(0, mnemonicIndex) + command.substring(mnemonicIndex + 1); - ContainerUtil.addIfNotNull(myShowHideHandlers, createMnemonicActionIfNeeded(fixedCommand, mnemonicIndex, action, myBaseComponent)); - myHtmlBuilder.append("").append(linkHtml).append(""); - endActionColumn(); - myActions.put(fixedCommand, action); - return this; - } - - public @NotNull Builder addDetailsButton() { - final String message = myError == null ? myMessage : myError.getStackTrace().length > 0 ? ExceptionUtil.getThrowableText(myError, "com.intellij.") : myError.getMessage(); - if (StringUtil.contains(myHtmlBuilder, message)) return this; - return addLink("details", DataGridBundle.message("action.details.text"), () -> Messages.showIdeaMessageDialog(null, message, - DataGridBundle.message("dialog.title.query.error"), - new String[]{CommonBundle.getOkButtonText()}, 0, Messages.getErrorIcon(), null)); - } - - public @NotNull Builder addFullMessageButtonIfNeeded() { - if (!isChoppedMessage) return this; - return addLink("details", DataGridBundle.message("action.full.message.text"), () -> Messages.showIdeaMessageDialog(null, myMessage, - DataGridBundle.message("dialog.title.query.error"), - new String[]{CommonBundle.getOkButtonText()}, 0, Messages.getErrorIcon(), null)); - - } - - public @NotNull Builder addCloseButton(Runnable action) { - return addIconLink(DataGridBundle.message("action.close.text"), DataGridBundle.message("tooltip.close.esc"), AllIcons.Actions.Close, action); - } - - public @NotNull ErrorNotificationPanel build() { - myHtmlBuilder.append("
"); - myHtmlBuilder.append(errorMessage); - myHtmlBuilder.append("
"); - ErrorNotificationPanel result = new ErrorNotificationPanel(myHtmlBuilder.toString(), myActions, myType); //NON-NLS - registerShowHideHandlers(result); - return result; - } - - private void startActionColumn() { - myHtmlBuilder.append("
"); - } - - private void endActionColumn() { - myHtmlBuilder.append("
"); - } - - private @NlsContexts.NotificationContent @NotNull String getNormalizedMessage(@NotNull Throwable error) { - String sourceMessage = StringUtil.notNullize(error.getMessage(), - DataGridBundle.message("notification.content.unknown.problem.occurred.see.details")) + "kgmsdkgmksdfgnmksndfgknskdfgnksndgkndfkgnsdkfgnskdngkndsfgksnkgnfksdngksdnfgksndkgnsdkfgnksdnfgkndkfgnskngfksnkgfnksdnfgksdnfgknsdkgnslgnskldfnglksnfgksnfgksnkfgnksdfngksdnfgksdngksndfgknsdkf"; - // In some cases source message contains stacktrace inside. Let's chop it - int divPos = sourceMessage.indexOf("\n\tat "); - if (divPos != -1) { - sourceMessage = sourceMessage.substring(0, divPos); - isChoppedMessage = true; - } - return getNormalized(sourceMessage); - } - - private @NlsContexts.NotificationContent @NotNull String getNormalized(@NlsContexts.NotificationContent @NotNull String sourceMessage) { - int lineLimit = StringUtil.lineColToOffset(sourceMessage, 5, 0); - int charLimit = 1024; - int limit = lineLimit == -1 || lineLimit > charLimit ? charLimit : lineLimit; - if (sourceMessage.length() > limit) isChoppedMessage = true; - return StringUtil.trimLog(sourceMessage, limit + 1); - } - - private void registerShowHideHandlers(@NotNull JComponent component) { - if (myShowHideHandlers.isEmpty()) return; - - component.addHierarchyListener(new HierarchyListener() { - private Disposable myShownDisposable; - @Override - public void hierarchyChanged(HierarchyEvent e) { - Component c = e.getComponent(); - if (c == null || (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) <= 0) return; - - if (c.isShowing()) { - myShownDisposable = Disposer.newDisposable(); - for (Consumer handler : myShowHideHandlers) { - handler.consume(myShownDisposable); - } - return; - } - - if (myShownDisposable != null) Disposer.dispose(myShownDisposable); - myShownDisposable = null; - } - }); - } - - private static @Nullable Consumer createMnemonicActionIfNeeded(final @NlsActions.ActionText String command, - final int mnemonicIndex, - final Runnable runnable, - final JComponent component) { - if (mnemonicIndex < 0) return null; - return (parentDisposable) -> { - DumbAwareAction a = new DumbAwareAction(command) { - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - runnable.run(); - } - }; - int modifiers = SystemInfo.isMac && !Registry.is("ide.mac.alt.mnemonic.without.ctrl") ? - InputEvent.ALT_MASK | InputEvent.CTRL_MASK : InputEvent.ALT_MASK; - KeyStroke keyStroke = KeyStroke.getKeyStroke(Character.toUpperCase(command.charAt(mnemonicIndex)), modifiers); - a.registerCustomShortcutSet(new CustomShortcutSet(keyStroke), component, parentDisposable); - }; - } - } -} diff --git a/grid/impl/src/run/ui/ErrorNotificationPanel.kt b/grid/impl/src/run/ui/ErrorNotificationPanel.kt new file mode 100644 index 000000000000..c4b28557c694 --- /dev/null +++ b/grid/impl/src/run/ui/ErrorNotificationPanel.kt @@ -0,0 +1,296 @@ +package com.intellij.database.run.ui + +import com.intellij.CommonBundle +import com.intellij.database.DataGridBundle +import com.intellij.database.datagrid.GridUtil +import com.intellij.icons.AllIcons +import com.intellij.ide.CopyProvider +import com.intellij.ide.IdeTooltipManager.Companion.getInstance +import com.intellij.ide.IdeTooltipManager.Companion.initPane +import com.intellij.ide.TextCopyProvider +import com.intellij.openapi.Disposable +import com.intellij.openapi.actionSystem.* +import com.intellij.openapi.actionSystem.UiDataProvider.Companion.wrapComponent +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.ui.MessageType +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.util.* +import com.intellij.openapi.util.registry.Registry.Companion.`is` +import com.intellij.openapi.util.text.StringUtil +import com.intellij.ui.HintHint +import com.intellij.ui.HyperlinkAdapter +import com.intellij.util.Consumer +import com.intellij.util.ObjectUtils +import com.intellij.util.containers.ContainerUtil +import com.intellij.util.ui.JBInsets +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil +import org.jetbrains.annotations.NonNls +import java.awt.BorderLayout +import java.awt.Color +import java.awt.Dimension +import java.awt.event.HierarchyEvent +import java.awt.event.HierarchyListener +import java.awt.event.InputEvent +import java.awt.event.KeyEvent +import javax.swing.* +import javax.swing.event.HyperlinkEvent + +class ErrorNotificationPanel private constructor( + htmlErrorMessage: @NlsContexts.NotificationContent String, + private val myActions: MutableMap, + private val myType: MessageType +) : JPanel(BorderLayout()) { + private val myMessagePane: JEditorPane + private val myCopyProvider: CopyProvider + + init { + setBorder(JBUI.Borders.empty(0, 4)) + + myMessagePane = initPane(htmlErrorMessage, HintHint() + .setAwtTooltip(false) + .setTextFg(getForeground()) + .setTextBg(getBackground()) + .setBorderColor(getBackground()) + .setBorderInsets(JBInsets.emptyInsets()), null) + myMessagePane.setBorder(null) + myMessagePane.addHyperlinkListener(object : HyperlinkAdapter() { + override fun hyperlinkActivated(e: HyperlinkEvent) { + performAction(e.description) + } + }) + myCopyProvider = object : TextCopyProvider() { + override fun getActionUpdateThread(): ActionUpdateThread { + return ActionUpdateThread.EDT + } + + override fun getTextLinesToCopy(): MutableCollection? { + val text = myMessagePane.getSelectedText() + return if (StringUtil.isEmpty(text)) null else mutableSetOf(text) + } + } + add(wrapComponent(myMessagePane, UiDataProvider { sink: DataSink? -> + sink!!.set(PlatformDataKeys.COPY_PROVIDER, this.myCopyProvider) + }), BorderLayout.CENTER) + + object : DumbAwareAction(DataGridBundle.message("action.close.text")) { + override fun actionPerformed(e: AnActionEvent) { + performAction(DataGridBundle.message("action.close.text")) + } + }.registerCustomShortcutSet(CustomShortcutSet(KeyEvent.VK_ESCAPE), this) + } + + private fun performAction(actionName: String?) { + myActions[actionName]?.run() + } + + override fun getBackground(): Color { + return type.popupBackground + } + + override fun getForeground(): Color { + return type.titleForeground + } + + private val type: MessageType + get() = ObjectUtils.chooseNotNull(myType, MessageType.ERROR) + + override fun getMinimumSize(): Dimension { + return JBUI.emptySize() + } + + val content: JComponent + get() = myMessagePane + + class Builder internal constructor( + private val myMessage: @NlsContexts.NotificationContent String?, + private val myError: Throwable?, + private val myBaseComponent: JComponent + ) { + private val myLongMessage: Boolean + + private val myActions: MutableMap = LinkedHashMap() + private val myShowHideHandlers: MutableList> = ArrayList>() + private val myHtmlBuilder = StringBuilder() + + private var isChoppedMessage = false + + private var myType: MessageType = MessageType.ERROR + + init { + var errorMessage = if (myMessage == null) if (myError == null) null else getNormalizedMessage(myError) + else getNormalized( + myMessage) + val font = getInstance().getTextFont(true) + val fm = myBaseComponent.getFontMetrics(font) + myLongMessage = SwingUtilities.computeStringWidth(fm, errorMessage) > myBaseComponent.getWidth() * 3 / 4 + if (errorMessage != null) { + errorMessage = StringUtil.escapeXmlEntities(errorMessage).replace("\n", "
") + } + + myHtmlBuilder.append("") + myHtmlBuilder.append("") + } + + fun messageType(type: MessageType): Builder { + myType = type + return this + } + + fun addIconLink(command: String?, tooltipText: @NlsContexts.Tooltip String?, realIcon: Icon, action: Runnable?): Builder { + val iconPath = GridUtil.getIconPath(realIcon) + + startActionColumn() + myHtmlBuilder.append("") + endActionColumn() + + if (action != null) { + myActions.put(command, action) + } + + return this + } + + fun addSpace(): Builder { + startActionColumn() + endActionColumn() + return this + } + + fun addLink(command: @NonNls String, linkHtml: @NlsActions.ActionText String, action: Runnable): Builder { + startActionColumn() + val mnemonicIndex = UIUtil.getDisplayMnemonicIndex(command) + val fixedCommand: @NlsSafe String = if (mnemonicIndex < 0) command else command.substring(0, mnemonicIndex) + command.substring(mnemonicIndex + 1) + ContainerUtil.addIfNotNull?>(myShowHideHandlers, + createMnemonicActionIfNeeded(fixedCommand, mnemonicIndex, action, myBaseComponent)) + myHtmlBuilder.append("").append(linkHtml).append("") + endActionColumn() + myActions.put(fixedCommand, action) + return this + } + + fun addDetailsButton(): Builder { + val message: String = (if (myError == null) myMessage + else if (myError.stackTrace.size > 0) com.intellij.util.ExceptionUtil.getThrowableText(myError, "com.intellij.") + else myError.message)!! + if (StringUtil.contains(myHtmlBuilder, message)) return this + return addLink("details", DataGridBundle.message("action.details.text"), Runnable { + Messages.showIdeaMessageDialog(null, message, + DataGridBundle.message("dialog.title.query.error"), + arrayOf(CommonBundle.getOkButtonText()), 0, Messages.getErrorIcon(), null) + }) + } + + fun addFullMessageButtonIfNeeded(): Builder { + if (!isChoppedMessage) return this + return addLink("details", DataGridBundle.message("action.full.message.text"), Runnable { + Messages.showIdeaMessageDialog(null, myMessage, + DataGridBundle.message("dialog.title.query.error"), + arrayOf(CommonBundle.getOkButtonText()), 0, Messages.getErrorIcon(), null + ) + }) + } + + fun addCloseButton(action: Runnable?): Builder { + return addIconLink(DataGridBundle.message("action.close.text"), DataGridBundle.message("tooltip.close.esc"), + AllIcons.Actions.Close, action) + } + + fun build(): ErrorNotificationPanel { + myHtmlBuilder.append("
") + myHtmlBuilder.append(errorMessage) + myHtmlBuilder.append("
") + val result = ErrorNotificationPanel(myHtmlBuilder.toString(), myActions, myType) //NON-NLS + registerShowHideHandlers(result) + return result + } + + private fun startActionColumn() { + myHtmlBuilder.append("
") + } + + private fun endActionColumn() { + myHtmlBuilder.append("
") + } + + private fun getNormalizedMessage(error: Throwable): @NlsContexts.NotificationContent String { + var sourceMessage = StringUtil.notNullize(error.message, + DataGridBundle.message( + "notification.content.unknown.problem.occurred.see.details")) + "kgmsdkgmksdfgnmksndfgknskdfgnksndgkndfkgnsdkfgnskdngkndsfgksnkgnfksdngksdnfgksndkgnsdkfgnksdnfgkndkfgnskngfksnkgfnksdnfgksdnfgknsdkgnslgnskldfnglksnfgksnfgksnkfgnksdfngksdnfgksdngksndfgknsdkf" + // In some cases source message contains stacktrace inside. Let's chop it + val divPos = sourceMessage.indexOf("\n\tat ") + if (divPos != -1) { + sourceMessage = sourceMessage.substring(0, divPos) + isChoppedMessage = true + } + return getNormalized(sourceMessage) + } + + private fun getNormalized(sourceMessage: @NlsContexts.NotificationContent String): @NlsContexts.NotificationContent String { + val lineLimit = StringUtil.lineColToOffset(sourceMessage, 5, 0) + val charLimit = 1024 + val limit = if (lineLimit == -1 || lineLimit > charLimit) charLimit else lineLimit + if (sourceMessage.length > limit) isChoppedMessage = true + return StringUtil.trimLog(sourceMessage, limit + 1) + } + + private fun registerShowHideHandlers(component: JComponent) { + if (myShowHideHandlers.isEmpty()) return + + component.addHierarchyListener(object : HierarchyListener { + private var myShownDisposable: Disposable? = null + override fun hierarchyChanged(e: HierarchyEvent) { + val c = e.component + if (c == null || (e.getChangeFlags() and HierarchyEvent.SHOWING_CHANGED.toLong()) <= 0) return + + if (c.isShowing()) { + myShownDisposable = Disposer.newDisposable() + for (handler in myShowHideHandlers) { + handler.consume(myShownDisposable) + } + return + } + + if (myShownDisposable != null) Disposer.dispose(myShownDisposable!!) + myShownDisposable = null + } + }) + } + + companion object { + private fun createMnemonicActionIfNeeded( + command: @NlsActions.ActionText String, + mnemonicIndex: Int, + runnable: Runnable, + component: JComponent? + ): Consumer? { + if (mnemonicIndex < 0) return null + return { parentDisposable: Disposable? -> + val a: DumbAwareAction = object : DumbAwareAction(command) { + override fun actionPerformed(e: AnActionEvent) { + runnable.run() + } + } + val modifiers = if (SystemInfo.isMac && !`is`( + "ide.mac.alt.mnemonic.without.ctrl") + ) InputEvent.ALT_MASK or InputEvent.CTRL_MASK + else InputEvent.ALT_MASK + val keyStroke = KeyStroke.getKeyStroke(command.get(mnemonicIndex).uppercaseChar().code, modifiers) + a.registerCustomShortcutSet(CustomShortcutSet(keyStroke), component, parentDisposable) + } as? Consumer + } + } + } + + companion object { + @JvmStatic + fun create(message: @NlsContexts.NotificationContent String?, error: Throwable?, baseComponent: JComponent): Builder { + return Builder(message, error, baseComponent) + } + } +}