diff --git a/README.md b/README.md
index f57f573..798694a 100644
--- a/README.md
+++ b/README.md
@@ -4,16 +4,15 @@
[![Banner](doc/img/kpxBanner.png)](https://github.com/sponsors/halirutan)
---
-I'm grateful this project is currently receives sponsorship of $4/month from
-[jlash13](https://github.com/jlash13),
+
+I'm grateful this project is currently receives sponsorship of \$4/month from
[Indrek Toom](https://github.com/m2ger),
-$2/month from
+\$2/month from
[Mac Adamarczuk](https://github.com/macalac),
[Frank Harper](https://github.com/franklinharper),
-[Hasnain Baxamoosa](https://github.com/hbaxamoosa),
-[Brian Levis](https://github.com/brianlevis),
[PCoetzeeDev](https://github.com/PCoetzeeDev),
-and $1/month from [14 other people](https://github.com/sponsors/halirutan).
+[baffler](https://github.com/baffler)
+and \$1/month from [13 other people](https://github.com/sponsors/halirutan).
- [Become a GitHub Sponsor and support the Key Promoter X development](https://github.com/sponsors/halirutan)
diff --git a/build.gradle.kts b/build.gradle.kts
index 29b80d3..8cb4448 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,7 +2,6 @@ import org.jetbrains.changelog.Changelog
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
fun properties(key: String) = providers.gradleProperty(key)
-fun environment(key: String) = providers.environmentVariable(key)
plugins {
id("java")
@@ -24,9 +23,7 @@ repositories {
dependencies {
implementation(libs.annotations)
intellijPlatform {
- val type = properties("platformType").get()
- val version = properties("platformVersion").get()
- create(type, version)
+ create(properties("platformType"), properties("platformVersion"))
instrumentationTools()
pluginVerifier()
zipSigner()
@@ -92,7 +89,7 @@ intellijPlatform {
}
}
- verifyPlugin {
+ pluginVerification {
ides {
recommended()
}
diff --git a/gradle.properties b/gradle.properties
index 8070b82..ae87af3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,15 +1,14 @@
pluginGroup=de.halirutan.keypromoterx
pluginName=Key Promoter X
pluginRepositoryUrl=https://github.com/halirutan/IntelliJ-Key-Promoter-X
-pluginVersion=2024.2.0
+pluginVersion=2024.2.2-beta-1
pluginSinceBuild=241
pluginUntilBuild=
# Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl
# See https://jb.gg/intellij-platform-builds-list for available build versions
-pluginVerifierIdeVersions=IC-231.8109.2, IC-232.8660.185, IC-241.14494.240
platformType=IC
platformVersion=2024.1.4
-gradleVersion=8.9
+gradleVersion=8.10.2
kotlin.stdlib.default.dependency=false
org.gradle.configuration-cache=false
org.gradle.caching=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ad95fe0..c060eb7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,11 +3,9 @@
annotations = "24.1.0"
# plugins
-kotlin = "1.9.23"
-changelog = "2.2.0"
-gradleIntelliJPlugin = "2.0.0-RC1"
-qodana = "0.1.13"
-kover = "0.7.6"
+changelog = "2.2.1"
+gradleIntelliJPlugin = "2.1.0"
+kotlin = "1.9.25"
[libraries]
annotations = { group = "org.jetbrains", name = "annotations", version.ref = "annotations" }
@@ -16,5 +14,3 @@ annotations = { group = "org.jetbrains", name = "annotations", version.ref = "an
changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
gradleIntelliJPlugin = { id = "org.jetbrains.intellij.platform", version.ref = "gradleIntelliJPlugin" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
-kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
-qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" }
diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index e958d9d..5cb7814 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -15,18 +15,11 @@
halirutan
com.intellij.modules.lang
-
-
- de.halirutan.keypromoterx.KeyPromoter
-
-
-
-
@@ -36,4 +29,11 @@
+
+
+
+
+
diff --git a/resources/messages/KeyPromoterBundle.properties b/resources/messages/KeyPromoterBundle.properties
index a3dcbab..c71c3df 100644
--- a/resources/messages/KeyPromoterBundle.properties
+++ b/resources/messages/KeyPromoterBundle.properties
@@ -9,7 +9,7 @@
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-kp.notification.startup=Sponsor now: Your support helps to dedicate more time to new releases and bug fixes!
+kp.notification.startup=Looking for sponsors: Your support drives innovation, new releases, and faster bug fixes!
kp.dialog.reset.statistic.text=Do you really want to reset your Key Promoter X statistics? This cannot be undone!
kp.dialog.reset.statistic.title=Reset Statistics
kp.dialog.tip=
Command {0} missed {1} time(s)Press {2} to close the dialog
diff --git a/src/de/halirutan/keypromoterx/KeyPromoter.java b/src/de/halirutan/keypromoterx/KeyPromoter.java
deleted file mode 100644
index fc8ccec..0000000
--- a/src/de/halirutan/keypromoterx/KeyPromoter.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (c) 2021 Patrick Scheibe, Dmitry Kashin, Athiele.
- *
- * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the distribution.
- *
- * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
- * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
- * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package de.halirutan.keypromoterx;
-
-import com.intellij.ide.ui.UISettings;
-import com.intellij.openapi.Disposable;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.ex.AnActionListener;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.util.registry.Registry;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.toolWindow.StripeButton;
-import com.intellij.util.messages.MessageBus;
-import de.halirutan.keypromoterx.statistic.KeyPromoterStatistics;
-import org.jetbrains.annotations.NotNull;
-
-import java.awt.*;
-import java.awt.event.AWTEventListener;
-import java.awt.event.InputEvent;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseEvent;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import static de.halirutan.keypromoterx.KeyPromoterNotification.ShowMode.showModeFromSettings;
-
-/**
- * The main component that is registered in plugin.xml. It will take care of catching UI events
- * and transfers them to {@link KeyPromoterAction} for inspection. Depending on the type of action (tool-window button,
- * menu entry, etc.) a balloon is shown and the statistic is updated.
- *
- * @author Patrick Scheibe, Dmitry Kashin
- */
-public class KeyPromoter implements AWTEventListener, AnActionListener, Disposable {
-
- private final Map withoutShortcutStats = Collections.synchronizedMap(new HashMap<>());
- private final KeyPromoterStatistics statsService = ApplicationManager.getApplication().getService(KeyPromoterStatistics.class);
- // Presentation and stats fields.
- private final KeyPromoterSettings keyPromoterSettings = ApplicationManager.getApplication().getService(KeyPromoterSettings.class);
- private static final String distractionFreeModeKey = "editor.distraction.free.mode";
- private boolean mouseDrag = false;
-
- public KeyPromoter() {
- MessageBus messageBus = ApplicationManager.getApplication().getMessageBus();
- messageBus.connect(this).subscribe(AnActionListener.TOPIC, this);
- long eventMask = AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.WINDOW_EVENT_MASK | AWTEvent.WINDOW_STATE_EVENT_MASK;
- Toolkit.getDefaultToolkit().addAWTEventListener(this, eventMask);
- }
-
- /**
- * Catches all UI events from the main IDEA AWT making it possible to inspect all mouse-clicks.
- * Note that on OSX this will not catch clicks on the (detached) menu bar.
- * It also takes care of handling drag events that we're not interested in.
- *
- * @param e event that is caught
- */
- @Override
- public void eventDispatched(AWTEvent e) {
- int id = e.getID();
- if (id == MouseEvent.MOUSE_DRAGGED) {
- mouseDrag = true;
- return;
- }
-
- if (id == MouseEvent.MOUSE_RELEASED && ((MouseEvent) e).getButton() == MouseEvent.BUTTON1) {
- if (!mouseDrag) {
- handleMouseEvent(e);
- }
- mouseDrag = false;
- }
- }
-
- /**
- * Transfers the event to {@link KeyPromoterAction} and inspects the results. Then, depending on the result and the
- * Key Promoter X settings, a balloon is shown with the shortcut tip and the statistic is updated.
- *
- * @param e event that is handled
- */
- private void handleMouseEvent(AWTEvent e) {
- if (e.getSource() instanceof StripeButton && keyPromoterSettings.isToolWindowButtonsEnabled()) {
- KeyPromoterAction action = new KeyPromoterAction(e);
- showTip(action, ActionType.MouseAction);
- }
- }
-
- @Override
- public void beforeActionPerformed(@NotNull AnAction action, AnActionEvent event) {
- final InputEvent input = event.getInputEvent();
- ActionType type;
- if (input instanceof MouseEvent) {
- type = ActionType.MouseAction;
- } else if (input instanceof KeyEvent) {
- type = ActionType.KeyboardAction;
- } else {
- return;
- }
-
- final String place = event.getPlace();
- KeyPromoterAction kpAction;
- if ("ToolwindowToolbar".equals(place)) {
- if (keyPromoterSettings.isToolWindowButtonsEnabled()) {
- kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.TOOL_WINDOW_BUTTON);
- showTip(kpAction, type);
- }
- } else if ("MainMenu".equals(place)) {
- if (keyPromoterSettings.isMenusEnabled()) {
- kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.MENU_ENTRY);
- showTip(kpAction, type);
- }
- } else if ("MainToolbar".equals(place)) {
- if (keyPromoterSettings.isToolbarButtonsEnabled()) {
- kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.MAIN_TOOLBAR);
- showTip(kpAction, type);
- }
- } else if (place.matches(".*Popup")) {
- if (keyPromoterSettings.isEditorPopupEnabled()) {
- kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.POPUP);
- showTip(kpAction, type);
- }
- } else if (keyPromoterSettings.isAllButtonsEnabled()) {
- kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.OTHER);
- showTip(kpAction, type);
- }
- }
-
- private void showTip(KeyPromoterAction action, ActionType type) {
- if (action == null
- || !action.isValid()
- || statsService.isSuppressed(action)
- || disabledInPresentationMode()
- || disabledInDistractionFreeMode()
- || SnoozeNotifier.isSnoozed()
- || type == ActionType.Unknown
- || action.isMutedByDefault()
- ) {
- return;
- }
-
- final String shortcut = action.getShortcut();
- if (!StringUtil.isEmpty(shortcut)) {
- if (type == ActionType.MouseAction) {
- statsService.registerAction(action);
- int count = statsService.get(action).count;
- if (count % keyPromoterSettings.getShowTipsClickCount() == 0) {
- KeyPromoterNotification.showTip(action, statsService.get(action).getCount(), showModeFromSettings(keyPromoterSettings));
- }
- } else if (type == ActionType.KeyboardAction) {
- statsService.registerShortcutUsed(action);
- }
-
- } else {
- final String ideaActionID = action.getIdeaActionID();
- withoutShortcutStats.putIfAbsent(ideaActionID, 0);
- withoutShortcutStats.put(ideaActionID, withoutShortcutStats.get(ideaActionID) + 1);
- if (keyPromoterSettings.getProposeToCreateShortcutCount() > 0 &&
- withoutShortcutStats.get(ideaActionID) % keyPromoterSettings.getProposeToCreateShortcutCount() == 0
- ) {
- if (!(type == ActionType.MouseAction && KeyPromoterUtils.hasMouseShortcut(ideaActionID))) {
- KeyPromoterNotification.askToCreateShortcut(action, showModeFromSettings(keyPromoterSettings));
- }
- }
- }
- }
-
- private boolean disabledInPresentationMode() {
- boolean isPresentationMode = UISettings.getInstance().getPresentationMode();
- return isPresentationMode && keyPromoterSettings.isDisabledInPresentationMode();
- }
-
- private boolean disabledInDistractionFreeMode() {
- final boolean isDistractionFreeMode = Registry.get(distractionFreeModeKey).asBoolean();
- return isDistractionFreeMode && keyPromoterSettings.isDisabledInDistractionFreeMode();
- }
-
- @Override
- public void dispose() {
- Toolkit.getDefaultToolkit().removeAWTEventListener(this);
- }
-
- enum ActionType {
- MouseAction,
- KeyboardAction,
- Unknown
- }
-}
diff --git a/src/de/halirutan/keypromoterx/KeyPromoterAWTListener.java b/src/de/halirutan/keypromoterx/KeyPromoterAWTListener.java
new file mode 100644
index 0000000..e179943
--- /dev/null
+++ b/src/de/halirutan/keypromoterx/KeyPromoterAWTListener.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2020 Patrick Scheibe, Dmitry Kashin, Athiele.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package de.halirutan.keypromoterx;
+
+import com.intellij.ide.AppLifecycleListener;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.toolWindow.StripeButton;
+import org.jetbrains.annotations.NotNull;
+
+import java.awt.*;
+import java.awt.event.AWTEventListener;
+import java.awt.event.MouseEvent;
+import java.util.List;
+
+public class KeyPromoterAWTListener implements AppLifecycleListener, AWTEventListener {
+
+ private boolean mouseDrag = false;
+
+ /**
+ * Adds the current instance as an AWT event listener, observing mouse events.
+ *
+ * @param commandLineArgs Ignored here
+ */
+ @Override
+ public void appFrameCreated(@NotNull List commandLineArgs) {
+ long eventMask = AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.WINDOW_EVENT_MASK | AWTEvent.WINDOW_STATE_EVENT_MASK;
+ Toolkit.getDefaultToolkit().addAWTEventListener(this, eventMask);
+ }
+
+ /**
+ * Listens for and dispatches AWT events, particularly mouse events.
+ * Handles events, and invokes corresponding methods based on the
+ * event types and states.
+ * It takes care to not mistake drag and drop events for mouse clicks.
+ *
+ * @param e the AWT event to be dispatched
+ */
+ @Override
+ public void eventDispatched(AWTEvent e) {
+ int id = e.getID();
+ if (id == MouseEvent.MOUSE_DRAGGED) {
+ mouseDrag = true;
+ return;
+ }
+
+ if (id == MouseEvent.MOUSE_RELEASED && ((MouseEvent) e).getButton() == MouseEvent.BUTTON1) {
+ if (!mouseDrag) {
+ handleMouseEvent(e);
+ }
+ mouseDrag = false;
+ }
+ }
+
+ /**
+ * Handles mouse events specifically related to tool window buttons.
+ * This method is triggered when a mouse event occurs and checks
+ * if the event source is an instance of {@code StripeButton}.
+ * If tool window button tips are enabled in the settings, it
+ * creates an instance of {@code KeyPromoterAction} and uses
+ * {@code KeyPromoterTipService} to show the tip.
+ *
+ * @param e the AWT event to handle, typically a mouse event
+ */
+ private void handleMouseEvent(AWTEvent e) {
+ if (e.getSource() instanceof StripeButton) {
+ KeyPromoterSettings keyPromoterSettings = ApplicationManager.getApplication().getService(KeyPromoterSettings.class);
+ if (keyPromoterSettings.isToolWindowButtonsEnabled()) {
+ KeyPromoterAction action = new KeyPromoterAction(e);
+ KeyPromoterTipService tipService = ApplicationManager.getApplication().getService(KeyPromoterTipService.class);
+ tipService.showTip(action, KeyPromoterTipService.ActionType.MouseAction);
+ }
+ }
+ }
+
+ /**
+ * This method is called when the application is about to be closed.
+ * It removes this instance as an AWT event listener.
+ *
+ * @param isRestart is ignored in this override
+ */
+ @Override
+ public void appWillBeClosed(boolean isRestart) {
+ Toolkit.getDefaultToolkit().removeAWTEventListener(this);
+ }
+}
diff --git a/src/de/halirutan/keypromoterx/KeyPromoterActionListener.java b/src/de/halirutan/keypromoterx/KeyPromoterActionListener.java
new file mode 100644
index 0000000..a4c33f5
--- /dev/null
+++ b/src/de/halirutan/keypromoterx/KeyPromoterActionListener.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2021 Patrick Scheibe, Dmitry Kashin, Athiele.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package de.halirutan.keypromoterx;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.ex.AnActionListener;
+import com.intellij.openapi.application.ApplicationManager;
+import org.jetbrains.annotations.NotNull;
+
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+
+import static de.halirutan.keypromoterx.KeyPromoterTipService.ActionType;
+
+/**
+ * KeyPromoterActionListener listens for actions performed within the application
+ * and processes them based on the action type and the place where the
+ * action occurred.
+ */
+public class KeyPromoterActionListener implements AnActionListener {
+
+
+ /**
+ * Handles actions performed within the application before they are executed,
+ * evaluating their type and origin to determine whether a Key Promoter tip should be shown.
+ *
+ * @param action the action that was performed
+ * @param event the event that triggered the action
+ */
+ @Override
+ public void beforeActionPerformed(@NotNull AnAction action, AnActionEvent event) {
+ final InputEvent input = event.getInputEvent();
+ ActionType type;
+ if (input instanceof MouseEvent) {
+ type = ActionType.MouseAction;
+ } else if (input instanceof KeyEvent) {
+ type = ActionType.KeyboardAction;
+ } else {
+ return;
+ }
+
+ final String place = event.getPlace();
+ KeyPromoterAction kpAction;
+ KeyPromoterSettings keyPromoterSettings = ApplicationManager.getApplication().getService(KeyPromoterSettings.class);
+ KeyPromoterTipService tipService = ApplicationManager.getApplication().getService(KeyPromoterTipService.class);
+ if ("ToolwindowToolbar".equals(place)) {
+ if (keyPromoterSettings.isToolWindowButtonsEnabled()) {
+ kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.TOOL_WINDOW_BUTTON);
+ tipService.showTip(kpAction, type);
+ }
+ } else if ("MainMenu".equals(place)) {
+ if (keyPromoterSettings.isMenusEnabled()) {
+ kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.MENU_ENTRY);
+ tipService.showTip(kpAction, type);
+ }
+ } else if ("MainToolbar".equals(place)) {
+ if (keyPromoterSettings.isToolbarButtonsEnabled()) {
+ kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.MAIN_TOOLBAR);
+ tipService.showTip(kpAction, type);
+ }
+ } else if (place.matches(".*Popup")) {
+ if (keyPromoterSettings.isEditorPopupEnabled()) {
+ kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.POPUP);
+ tipService.showTip(kpAction, type);
+ }
+ } else if (keyPromoterSettings.isAllButtonsEnabled()) {
+ kpAction = new KeyPromoterAction(action, event, KeyPromoterAction.ActionSource.OTHER);
+ tipService.showTip(kpAction, type);
+ }
+ }
+}
diff --git a/src/de/halirutan/keypromoterx/KeyPromoterConfiguration.java b/src/de/halirutan/keypromoterx/KeyPromoterConfiguration.java
index b855c3b..c50a0f1 100644
--- a/src/de/halirutan/keypromoterx/KeyPromoterConfiguration.java
+++ b/src/de/halirutan/keypromoterx/KeyPromoterConfiguration.java
@@ -142,9 +142,6 @@ public void reset() {
myShowClickCount.setValue(keyPromoterSettings.getShowTipsClickCount());
}
- public void disposeUIResources() {
- }
-
public KeyPromoterSettings getSettings() {
return keyPromoterSettings;
}
diff --git a/src/de/halirutan/keypromoterx/KeyPromoterDialog.java b/src/de/halirutan/keypromoterx/KeyPromoterDialog.java
index 3e0e8aa..28b51f3 100644
--- a/src/de/halirutan/keypromoterx/KeyPromoterDialog.java
+++ b/src/de/halirutan/keypromoterx/KeyPromoterDialog.java
@@ -10,12 +10,11 @@
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
-import java.awt.BorderLayout;
+import java.awt.*;
import static com.intellij.openapi.application.ApplicationManager.getApplication;
public class KeyPromoterDialog extends DialogWrapper {
- private static final KeyPromoterSettings mySettings = getApplication().getService(KeyPromoterSettings.class);
private final KeyPromoterAction action;
private final JLabel label;
@@ -48,6 +47,7 @@ protected JComponent createCenterPanel() {
@Override
public void show() {
+ KeyPromoterSettings mySettings = getApplication().getService(KeyPromoterSettings.class);
if (mySettings.hardMode) {
getApplication().invokeLater(super::show);
}
diff --git a/src/de/halirutan/keypromoterx/KeyPromoterNotification.java b/src/de/halirutan/keypromoterx/KeyPromoterNotification.java
index 4a77db6..b2a221a 100644
--- a/src/de/halirutan/keypromoterx/KeyPromoterNotification.java
+++ b/src/de/halirutan/keypromoterx/KeyPromoterNotification.java
@@ -29,12 +29,23 @@
*/
public class KeyPromoterNotification {
- private static final NotificationGroup GROUP = NotificationGroupManager.getInstance().getNotificationGroup(
- KeyPromoterBundle.message("kp.notification.group")
- );
+ /**
+ * Gets the Key Promoter X notification group on demand.
+ *
+ * @return Key Promoter X notification group
+ */
+ private static NotificationGroup getKeyPromoterNotificationGroup() {
+ return NotificationGroupManager.getInstance().getNotificationGroup(
+ KeyPromoterBundle.message("kp.notification.group"));
+ }
+ /**
+ * Displays a startup notification for the Key Promoter plugin.
+ * This notification informs the user with a message from the Key Promoter bundle.
+ * It also sets the notification as important and includes a suggestion link.
+ */
public static void showStartupNotification() {
- final Notification notification = GROUP.createNotification(KeyPromoterBundle.message(
+ final Notification notification = getKeyPromoterNotificationGroup().createNotification(KeyPromoterBundle.message(
"kp.notification.group"),
KeyPromoterBundle.message("kp.notification.startup"),
NotificationType.INFORMATION)
@@ -48,7 +59,14 @@ public static void showStartupNotification() {
notification.notify(null);
}
- static void showTip(KeyPromoterAction action, int count, ShowMode mode) {
+ /**
+ * Displays a notification based on the provided action, count, and show mode.
+ *
+ * @param action The KeyPromoterAction that triggered the notification.
+ * @param count The frequency or count related to the action.
+ * @param mode The mode for showing the notification, either as a notification or a dialog.
+ */
+ static void displayNotification(KeyPromoterAction action, int count, ShowMode mode) {
String title = "";
switch (mode) {
case NOTIFICATION -> title = KeyPromoterBundle.message("kp.notification.tip.title", action.getDescription());
@@ -57,6 +75,12 @@ static void showTip(KeyPromoterAction action, int count, ShowMode mode) {
mode.showTip(title, action, count);
}
+ /**
+ * Prompts the user to create a shortcut for a given KeyPromoterAction.
+ *
+ * @param action The KeyPromoterAction for which the user is being prompted.
+ * @param mode The display mode which determines how the prompt will be shown.
+ */
static void askToCreateShortcut(KeyPromoterAction action, ShowMode mode) {
String title = KeyPromoterBundle.message("kp.notification.group");
String message = KeyPromoterBundle.message("kp.notification.ask.new.shortcut", action.getDescription());
@@ -88,22 +112,10 @@ public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification noti
}
}
- private static class SuppressTipAction extends NotificationAction {
- private final KeyPromoterStatistics statistics = getApplication().getService(KeyPromoterStatistics.class);
- private final KeyPromoterAction myAction;
-
- SuppressTipAction(KeyPromoterAction action) {
- super(KeyPromoterBundle.message("kp.notification.disable.message"));
- myAction = action;
- }
-
- @Override
- public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
- statistics.suppressItem(myAction);
- notification.expire();
- }
- }
-
+ /**
+ * Enum representing different modes to show tips in the Key Promoter plugin.
+ * It has two modes: NOTIFICATION and DIALOG.
+ */
public enum ShowMode {
NOTIFICATION {
@Override
@@ -111,7 +123,7 @@ public void showTip(String title, KeyPromoterAction action, int count) {
String countMessage = count > 1 ? count + " times" : count + " time";
String message = KeyPromoterBundle.message("kp.notification.tip.message", action.getShortcut(), countMessage);
- Notification notification = GROUP.createNotification(title, message, NotificationType.INFORMATION)
+ Notification notification = getKeyPromoterNotificationGroup().createNotification(title, message, NotificationType.INFORMATION)
.setIcon(KeyPromoterIcons.KP_ICON)
.addAction(new EditKeymapAction(action, KeyPromoterBundle.message("kp.notification.edit.shortcut")))
.addAction(new SuppressTipAction(action));
@@ -135,11 +147,32 @@ public static ShowMode showModeFromSettings(KeyPromoterSettings settings) {
public abstract void showTip(String title, KeyPromoterAction action, int count);
public void askToCreateShortcut(String title, String message, KeyPromoterAction action) {
- Notification notification = GROUP.createNotification(title, message, NotificationType.INFORMATION)
+ Notification notification = getKeyPromoterNotificationGroup().createNotification(title, message, NotificationType.INFORMATION)
.setIcon(KeyPromoterIcons.KP_ICON)
.addAction(new EditKeymapAction(action))
.addAction(new SuppressTipAction(action));
notification.notify(null);
}
}
+
+ /**
+ * SuppressTipAction is an action that allows users to suppress tips from the Key Promoter plugin.
+ * When triggered, this action will mark the provided KeyPromoterAction as suppressed in the statistics.
+ * This effectively stops further notifications related to this action from being shown.
+ */
+ private static class SuppressTipAction extends NotificationAction {
+ private final KeyPromoterStatistics statistics = getApplication().getService(KeyPromoterStatistics.class);
+ private final KeyPromoterAction myAction;
+
+ SuppressTipAction(KeyPromoterAction action) {
+ super(KeyPromoterBundle.message("kp.notification.disable.message"));
+ myAction = action;
+ }
+
+ @Override
+ public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
+ statistics.suppressItem(myAction);
+ notification.expire();
+ }
+ }
}
diff --git a/src/de/halirutan/keypromoterx/KeyPromoterTipService.java b/src/de/halirutan/keypromoterx/KeyPromoterTipService.java
new file mode 100644
index 0000000..dfa3b7d
--- /dev/null
+++ b/src/de/halirutan/keypromoterx/KeyPromoterTipService.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2020 Patrick Scheibe, Dmitry Kashin, Athiele.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package de.halirutan.keypromoterx;
+
+import com.intellij.ide.ui.UISettings;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.Service;
+import com.intellij.openapi.util.registry.Registry;
+import com.intellij.openapi.util.text.StringUtil;
+import de.halirutan.keypromoterx.statistic.KeyPromoterStatistics;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static de.halirutan.keypromoterx.KeyPromoterNotification.ShowMode.showModeFromSettings;
+
+/**
+ * The KeyPromoterTipService is responsible for displaying tips and notifications to
+ * users regarding the usage of shortcuts in the application.
+ */
+@Service(Service.Level.APP)
+public final class KeyPromoterTipService {
+
+ private static final String distractionFreeModeKey = "editor.distraction.free.mode";
+ private final Map withoutShortcutStats = Collections.synchronizedMap(new HashMap<>());
+
+ /**
+ * Displays a tip or notification corresponding to the given action.
+ * Depending on the action type and the configured settings, this method
+ * will either record the action, show a tip, or propose creating a shortcut
+ * for actions that do not have an associated shortcut.
+ *
+ * @param action The action for which the tip or notification is being shown.
+ * It must be valid and not suppressed, and should not be muted by default.
+ * @param type The type of the action, which influences how the tip or notification is displayed.
+ * The method does nothing for actions of type {@code ActionType.Unknown}.
+ */
+ public void showTip(KeyPromoterAction action, ActionType type) {
+ KeyPromoterStatistics statsService = ApplicationManager.getApplication().getService(KeyPromoterStatistics.class);
+ KeyPromoterSettings keyPromoterSettings = ApplicationManager.getApplication().getService(KeyPromoterSettings.class);
+
+ if (action == null
+ || !action.isValid()
+ || statsService.isSuppressed(action)
+ || disabledInPresentationMode()
+ || disabledInDistractionFreeMode()
+ || SnoozeNotifier.isSnoozed()
+ || type == ActionType.Unknown
+ || action.isMutedByDefault()
+ ) {
+ return;
+ }
+
+ final String shortcut = action.getShortcut();
+ if (!StringUtil.isEmpty(shortcut)) {
+ if (type == ActionType.MouseAction) {
+ statsService.registerAction(action);
+ int count = statsService.get(action).count;
+ if (count % keyPromoterSettings.getShowTipsClickCount() == 0) {
+ KeyPromoterNotification.displayNotification(action, statsService.get(action).getCount(), showModeFromSettings(keyPromoterSettings));
+ }
+ } else if (type == ActionType.KeyboardAction) {
+ statsService.registerShortcutUsed(action);
+ }
+
+ } else {
+ final String ideaActionID = action.getIdeaActionID();
+ withoutShortcutStats.putIfAbsent(ideaActionID, 0);
+ withoutShortcutStats.put(ideaActionID, withoutShortcutStats.get(ideaActionID) + 1);
+ if (keyPromoterSettings.getProposeToCreateShortcutCount() > 0 &&
+ withoutShortcutStats.get(ideaActionID) % keyPromoterSettings.getProposeToCreateShortcutCount() == 0
+ ) {
+ if (!(type == ActionType.MouseAction && KeyPromoterUtils.hasMouseShortcut(ideaActionID))) {
+ KeyPromoterNotification.askToCreateShortcut(action, showModeFromSettings(keyPromoterSettings));
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the KeyPromoter plugin is disabled in presentation mode.
+ *
+ * @return {@code true} if the plugin is disabled in presentation mode, {@code false} otherwise
+ */
+ private boolean disabledInPresentationMode() {
+ KeyPromoterSettings keyPromoterSettings = ApplicationManager.getApplication().getService(KeyPromoterSettings.class);
+ boolean isPresentationMode = UISettings.getInstance().getPresentationMode();
+ return isPresentationMode && keyPromoterSettings.isDisabledInPresentationMode();
+ }
+
+ /**
+ * Checks if the KeyPromoter plugin is disabled in distraction-free mode.
+ *
+ * @return {@code true} if the plugin is disabled in distraction-free mode, {@code false} otherwise
+ */
+ private boolean disabledInDistractionFreeMode() {
+ KeyPromoterSettings keyPromoterSettings = ApplicationManager.getApplication().getService(KeyPromoterSettings.class);
+ final boolean isDistractionFreeMode = Registry.get(distractionFreeModeKey).asBoolean();
+ return isDistractionFreeMode && keyPromoterSettings.isDisabledInDistractionFreeMode();
+ }
+
+ /**
+ * Represents the type of action performed within the application.
+ * This type can influence how tips or notifications are displayed.
+ */
+ public enum ActionType {
+ MouseAction,
+ KeyboardAction,
+ Unknown
+ }
+
+}
diff --git a/src/de/halirutan/keypromoterx/KeyPromoterUtils.java b/src/de/halirutan/keypromoterx/KeyPromoterUtils.java
index 5799e58..d27e2ee 100644
--- a/src/de/halirutan/keypromoterx/KeyPromoterUtils.java
+++ b/src/de/halirutan/keypromoterx/KeyPromoterUtils.java
@@ -28,9 +28,6 @@
*/
class KeyPromoterUtils {
- private static final KeymapManager keyMapManager = KeymapManager.getInstance();
- private static final KeyPromoterSettings mySettings = ApplicationManager.getApplication().getService(KeyPromoterSettings.class);
-
/**
* Get first field of class with target type to use during click source handling.
*
@@ -38,7 +35,7 @@ class KeyPromoterUtils {
* @param targetClass target class to check field to plug
* @return field
*/
- static Field getFieldOfType(Class> aClass, Class> targetClass) {
+ static Field getFieldOfType(Class> aClass, @SuppressWarnings("SameParameterValue") Class> targetClass) {
do {
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
@@ -57,6 +54,7 @@ static Field getFieldOfType(Class> aClass, Class> targetClass) {
* @return true if a mouse shortcut exists in the keymap
*/
public static boolean hasMouseShortcut(String actionId) {
+ KeymapManager keyMapManager = KeymapManager.getInstance();
final Keymap activeKeymap = keyMapManager.getActiveKeymap();
return Arrays.stream(activeKeymap.getShortcuts(actionId)).anyMatch(shortcut -> !shortcut.isKeyboard());
}
@@ -68,8 +66,10 @@ public static boolean hasMouseShortcut(String actionId) {
* @return a string combining one or more shortcuts
*/
static String getKeyboardShortcutsText(String myIdeaActionID) {
+ KeymapManager keyMapManager = KeymapManager.getInstance();
final Keymap activeKeymap = keyMapManager.getActiveKeymap();
Shortcut[] shortcuts;
+ KeyPromoterSettings mySettings = ApplicationManager.getApplication().getService(KeyPromoterSettings.class);
if (mySettings.isShowKeyboardShortcutsOnly()) {
shortcuts = Arrays.stream(
activeKeymap.getShortcuts(myIdeaActionID)
diff --git a/src/de/halirutan/keypromoterx/KeyPromoterXStartupNotification.java b/src/de/halirutan/keypromoterx/KeyPromoterXStartupNotification.java
index 2c17a1e..7d21bac 100644
--- a/src/de/halirutan/keypromoterx/KeyPromoterXStartupNotification.java
+++ b/src/de/halirutan/keypromoterx/KeyPromoterXStartupNotification.java
@@ -28,17 +28,20 @@
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.startup.StartupActivity;
+import com.intellij.openapi.startup.ProjectActivity;
import com.intellij.util.text.VersionComparatorUtil;
+import kotlin.Unit;
+import kotlin.coroutines.Continuation;
import org.jetbrains.annotations.NotNull;
/**
* Provides an information balloon at the start of the IDE when a new version is installed.
*/
-public class KeyPromoterXStartupNotification implements StartupActivity, DumbAware {
+public class KeyPromoterXStartupNotification implements ProjectActivity, DumbAware {
+
@Override
- public void runActivity(@NotNull Project project) {
- if (ApplicationManager.getApplication().isUnitTestMode()) return;
+ public Object execute(@NotNull Project project, @NotNull Continuation super Unit> continuation) {
+ if (ApplicationManager.getApplication().isUnitTestMode()) return null;
final KeyPromoterSettings settings = ApplicationManager.getApplication().getService(KeyPromoterSettings.class);
final String installedVersion = settings.getInstalledVersion();
@@ -52,6 +55,7 @@ public void runActivity(@NotNull Project project) {
settings.setInstalledVersion(plugin.getVersion());
}
}
+ return null;
}
}
diff --git a/src/de/halirutan/keypromoterx/statistic/StatisticsList.java b/src/de/halirutan/keypromoterx/statistic/StatisticsList.java
index 598d21f..42e6fd6 100644
--- a/src/de/halirutan/keypromoterx/statistic/StatisticsList.java
+++ b/src/de/halirutan/keypromoterx/statistic/StatisticsList.java
@@ -26,12 +26,14 @@
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import java.io.Serial;
/**
* Provides a custom JBList for displaying how often a button was pressed that could have been replaced by a shortcut.
* The list is backed by {@link KeyPromoterStatistics} that keeps the values persistent through restarts.
*/
public class StatisticsList extends JBList implements PropertyChangeListener {
+ @Serial
private static final long serialVersionUID = 20212;
private final DefaultListModel myModel;
private final KeyPromoterStatistics myStats = ApplicationManager.getApplication().getService(KeyPromoterStatistics.class);
@@ -113,6 +115,7 @@ private void updateStats() {
* Provides custom rendering of items in the Key Promoter X statistic tool-window.
*/
static class StatisticsItemCellRenderer extends JLabel implements ListCellRenderer {
+ @Serial
private static final long serialVersionUID = 20212;
@Override
@@ -129,7 +132,7 @@ public JLabel getListCellRendererComponent(JList extends StatisticsItem> list,
setIcon(KeyPromoterIcons.KP_ICON);
setForeground(foreground);
setBorder(JBUI.Borders.empty(2, 10));
- if (value.ideaActionID != null && !"".equals(value.ideaActionID)) {
+ if (value.ideaActionID != null && !value.ideaActionID.isEmpty()) {
final AnAction action = ActionManager.getInstance().getAction(value.ideaActionID);
if (action != null) {
final Icon icon = action.getTemplatePresentation().getIcon();
diff --git a/src/de/halirutan/keypromoterx/statistic/SuppressedList.java b/src/de/halirutan/keypromoterx/statistic/SuppressedList.java
index 2098445..7e41b5c 100644
--- a/src/de/halirutan/keypromoterx/statistic/SuppressedList.java
+++ b/src/de/halirutan/keypromoterx/statistic/SuppressedList.java
@@ -27,6 +27,7 @@
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import java.io.Serial;
import java.util.EventListener;
import java.util.Objects;
@@ -37,6 +38,7 @@
* @author Patrick Scheibe
*/
public class SuppressedList extends JBList implements PropertyChangeListener, EventListener {
+ @Serial
private static final long serialVersionUID = 20212;
private final KeyPromoterStatistics myStats = ApplicationManager.getApplication().getService(KeyPromoterStatistics.class);
private final DefaultListModel myModel;
@@ -111,6 +113,7 @@ private void unsuppressItem() {
* Provides custom rendering of items in the Key Promoter X statistic tool-window.
*/
static class SuppressedItemCellRenderer extends JLabel implements ListCellRenderer {
+ @Serial
private static final long serialVersionUID = 20212;
SuppressedItemCellRenderer() {
@@ -144,7 +147,7 @@ public Component getListCellRendererComponent(JList extends StatisticsItem> li
setForeground(foreground);
setBorder(JBUI.Borders.empty(2, 10));
- if (value.ideaActionID != null && !"".equals(value.ideaActionID)) {
+ if (value.ideaActionID != null && !value.ideaActionID.isEmpty()) {
final AnAction action = ActionManager.getInstance().getAction(value.ideaActionID);
if (action != null) {