IDEA-245047 ui: fix duplicated entries in git menu
authorAleksey Pivovarov <AMPivovarov@gmail.com>
Mon, 3 Aug 2020 22:08:01 +0000 (01:08 +0300)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Wed, 12 Aug 2020 18:57:34 +0000 (18:57 +0000)
Add workaround for 'JMenu$MenuChangeListener' implementation, that allows recursive 'menuSelected' events.
It checks 'isSelected' flag on every ChangeEvent and updates its value only after notifying all listeners.
If one of the listeners changes something unrelated in the model (ex: button mnemonic), 'fireMenuSelected()' events might be recursively fired again.

GitOrigin-RevId: e9e699573b492b472787cddb61f5b6d403cab099

platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.java

index 7289d6341d88fb31cebb788a36c84264d7a87b03..f9c6e1e2321430548433b63649ad95f0d2658da7 100644 (file)
@@ -29,6 +29,8 @@ import com.intellij.util.ui.UIUtil;
 import org.jetbrains.annotations.NotNull;
 
 import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 import javax.swing.event.MenuEvent;
 import javax.swing.event.MenuListener;
 import java.awt.*;
@@ -152,9 +154,12 @@ public final class ActionMenu extends JBMenu {
 
     myStubItem = macSystemMenu ? null : new StubItem();
     addStubItem();
-    addMenuListener(new MenuListenerImpl());
     setBorderPainted(false);
 
+    MenuListenerImpl menuListener = new MenuListenerImpl();
+    addMenuListener(menuListener);
+    getModel().addChangeListener(menuListener);
+
     setVisible(myPresentation.isVisible());
     setEnabled(myPresentation.isEnabled());
     setText(myPresentation.getText());
@@ -235,8 +240,30 @@ public final class ActionMenu extends JBMenu {
     }
   }
 
-  private class MenuListenerImpl implements MenuListener {
+  private class MenuListenerImpl implements ChangeListener, MenuListener {
+    boolean isSelected = false;
+
     boolean myIsHidden = false;
+
+    @Override
+    public void stateChanged(ChangeEvent e) {
+      // Re-implement javax.swing.JMenu.MenuChangeListener to avoid recursive event notifications
+      // if 'menuSelected' fires unrelated 'stateChanged' event, without changing 'model.isSelected()' value.
+      ButtonModel model = (ButtonModel)e.getSource();
+      boolean modelSelected = model.isSelected();
+
+      if (modelSelected != isSelected) {
+        isSelected = modelSelected;
+
+        if (modelSelected) {
+          menuSelected();
+        }
+        else {
+          menuDeselected();
+        }
+      }
+    }
+
     @Override
     public void menuCanceled(MenuEvent e) {
       onMenuHidden();
@@ -244,6 +271,15 @@ public final class ActionMenu extends JBMenu {
 
     @Override
     public void menuDeselected(MenuEvent e) {
+      // Use ChangeListener instead to guard against recursive calls
+    }
+
+    @Override
+    public void menuSelected(MenuEvent e) {
+      // Use ChangeListener instead to guard against recursive calls
+    }
+
+    private void menuDeselected() {
       if (myDisposable != null) {
         Disposer.dispose(myDisposable);
         myDisposable = null;
@@ -286,8 +322,7 @@ public final class ActionMenu extends JBMenu {
       }
     }
 
-    @Override
-    public void menuSelected(MenuEvent e) {
+    private void menuSelected() {
       UsabilityHelper helper = new UsabilityHelper(ActionMenu.this);
       if (myDisposable == null) {
         myDisposable = Disposer.newDisposable();