javaswinglook-and-feeljmenujmenuitem

How change the color arrowIcon to JMenu when it is selected


I have a problem with the arrowIcon of the JMenu: when i mouse-over it, it loses the color that i have set for it.

This is the minimal example

public class DemoLookAndFeel extends JFrame {

    private JMenuBar menuBar = new JMenuBar();
    private JMenu arrowMenuOne = new JMenu("Root Menu 1");
    private JMenu arrowMenuTwo = new JMenu("Root Menu 2");

    static {
        UIManager.put("MenuItem.selectionForeground", Color.MAGENTA);
        UIManager.put("MenuItem.foreground",  Color.MAGENTA);
        UIManager.put("Menu.selectionForeground", Color.MAGENTA);
        UIManager.put("Menu.foreground",  Color.MAGENTA);
    }

    public void init() {

        setJMenuBar(menuBar);

        addSubMenus(arrowMenuOne, 5);
        addSubMenus(arrowMenuTwo, 3);

        menuBar.add(arrowMenuOne);
        menuBar.add(arrowMenuTwo);

        this.setSize(800,800);
        this.pack();
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    public void addSubMenus(JMenu parent, int number) {
        for (int i = 1; i <= number; i++) {
            JMenu menu = new JMenu("Sub Menu " + i);
            parent.add(menu);

            addSubMenus(menu, number - 1);
            addMenuItems(menu, number);
        }
    }

    public void addMenuItems(JMenu parent, int number) {
        for (int i = 1; i <= number; i++) {
            parent.add(new JMenuItem("Item " + i));
        }
    }


    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                DemoLookAndFeel demo = new DemoLookAndFeel();
                demo.init();
            }
        });
    }

}

Please note that some parts of this code have been taken from other answers on stack overflow.

I know that the icon is painted with this code from BasiMenuUI, but still i can't understand why i am not getting the desired behaviour.

private void paintArrowIcon(Graphics g, MenuItemLayoutHelper lh,
                                MenuItemLayoutHelper.LayoutResult lr,
                                Color foreground) {
        if (lh.getArrowIcon() != null) {
            ButtonModel model = lh.getMenuItem().getModel();
            if (model.isArmed() || (lh.getMenuItem() instanceof JMenu
                                && model.isSelected())) {
                g.setColor(foreground);
            }
            if (lh.useCheckAndArrow()) {
                lh.getArrowIcon().paintIcon(lh.getMenuItem(), g,
                        lr.getArrowRect().x, lr.getArrowRect().y);
            }
        }
    }

Can you please help me? thanks


Solution

  • when i mouse-over it, it loses the color that i have set for it.

    This is because MetalTheme of MetalLookAndFeel ignores BasicLookAndFeel color settings as follows.

    /* @see javax/swing/plaf/metal/MetalIconFactory.java */
    class MenuArrowIcon implements Icon {
      @Override public void paintIcon(Component c, Graphics g, int x, int y) {
        JMenuItem b = (JMenuItem) c;
        ButtonModel model = b.getModel();
    
        g.translate(x, y);
        if (!model.isEnabled()) {
          g.setColor(MetalLookAndFeel.getMenuDisabledForeground());
        } else {
          if (model.isArmed() || (c instanceof JMenu && model.isSelected())) {
            g.setColor(MetalLookAndFeel.getMenuSelectedForeground());
            // use Color.MAGENTA: g.setColor(UIManager.getColor("Menu.selectedForeground"));
          } else {
            g.setColor(b.getForeground());
          }
        }
        // if (MetalUtils.isLeftToRight(b)) {
          int[] xPoints = {0, 3, 3, 0};
          int[] yPoints = {0, 3, 4, 7};
          g.fillPolygon(xPoints, yPoints, 4);
          g.drawPolygon(xPoints, yPoints, 4);
        // } else {
        //   int[] xPoints = {4, 4, 1, 1};
        //   int[] yPoints = {0, 7, 4, 3};
        //   g.fillPolygon(xPoints, yPoints, 4);
        //   g.drawPolygon(xPoints, yPoints, 4);
        // }
        g.translate(-x, -y);
      }
    
      @Override public int getIconWidth() {
        return 4;
      }
    
      @Override public int getIconHeight() {
        return 8;
      }
    }
    

    To change the selection foreground color of the menu arrow icon, you might need to change MetalTheme or set your ownMenuArrowIcon:

    import java.awt.*;
    import javax.swing.*;
    import javax.swing.plaf.*;
    import javax.swing.plaf.metal.*;
    
    public class DemoLookAndFeel2 extends JFrame {
      private JMenuBar menuBar = new JMenuBar();
      private JMenu arrowMenuOne = new JMenu("Root Menu 1");
      private JMenu arrowMenuTwo = new JMenu("Root Menu 2");
    
      static {
        UIManager.put("MenuItem.selectionForeground", Color.MAGENTA);
        UIManager.put("MenuItem.foreground", Color.MAGENTA);
        UIManager.put("Menu.selectionForeground", Color.MAGENTA);
        UIManager.put("Menu.foreground", Color.MAGENTA);
        // or: UIManager.put("Menu.arrowIcon", new MenuArrowIcon());
      }
    
      public void init() {
        setJMenuBar(menuBar);
        addSubMenus(arrowMenuOne, 5);
        addSubMenus(arrowMenuTwo, 3);
    
        menuBar.add(arrowMenuOne);
        menuBar.add(arrowMenuTwo);
    
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.setSize(800, 800);
        // this.pack();
        this.setLocationRelativeTo(null);
        this.setVisible(true);
      }
    
      public void addSubMenus(JMenu parent, int number) {
        for (int i = 1; i <= number; i++) {
          JMenu menu = new JMenu("Sub Menu " + i);
          parent.add(menu);
    
          addSubMenus(menu, number - 1);
          addMenuItems(menu, number);
        }
      }
    
      public void addMenuItems(JMenu parent, int number) {
        for (int i = 1; i <= number; i++) {
          parent.add(new JMenuItem("Item " + i));
        }
      }
    
      public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override public void run() {
            MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme() {
              @Override public ColorUIResource getMenuSelectedForeground() {
                return new ColorUIResource(Color.MAGENTA);
              };
            });
            DemoLookAndFeel2 demo = new DemoLookAndFeel2();
            demo.init();
          }
        });
      }
    }