4

Seems like I've found a bug in Java:

I need to create the JFrame with a transparent background and now I need to show the JPopupMenu for some user actions. It works fine when JPopupMenu is housed fully inside a JFrame. But when the JPopupMenu is partly outside the JFrame, no item is visible.

SSCCE:

public class PopupTest {
    public static void main(String[] a) {
        final JFrame frame = new JFrame();
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createLineBorder(Color.RED));

        panel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON3) {
                    JPopupMenu menu = new JPopupMenu();
                    for (int i = 0 ; i < 10; i++) {
                        menu.add(String.valueOf(i));
                    }

                    menu.show(panel, e.getX(), e.getY());
                }
            }
        });
        frame.setContentPane(panel);
        frame.setUndecorated(true);
        frame.setBackground(new Color(50, 50, 50, 200));

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.setVisible(true);
            }
        });
    }
}

Does anyone know how to solve this?

PS: JDK 7u40, Win x64

Nathan
  • 8,093
  • 8
  • 50
  • 76
SeniorJD
  • 6,946
  • 4
  • 36
  • 53
  • Popup displays okay for me when partly outside the frame boundary (running Kubuntu 13.04 + OpenJDK 7 64-bit) – Ash Sep 20 '13 at 13:51
  • can't simulating in Win7 JDKx_10, x_25 or x_40, but there I catch some interesting anomalies with implemented Java8 pre_release/support in Java6/7_40, need/have to test – mKorbel Sep 20 '13 at 15:28
  • @mKorbel try to replace 10 with 100 `for` cycle – SeniorJD Sep 20 '13 at 15:55
  • @SeniorJD :-) without success I can see there Item 0-51 (51 is selectable by mouse, but out off screen) – mKorbel Sep 20 '13 at 18:37
  • my Win8/64 at office is sensitive to all Official bugs from bugparade for Java6/7, my question are you compile in JDK 7_040 or JRE is ..., running from IDE or from compiled *.jar file – mKorbel Sep 20 '13 at 18:39
  • and there are another two issues 1) frame.add(panel); use frame.setContentPane(panel); then you must to change opacity for JPanel, 2) JButton is transparent only with UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel"); – mKorbel Sep 20 '13 at 19:00
  • @mKorbel 1. both compiled and from IDE version have current bug. – SeniorJD Sep 20 '13 at 19:24
  • @SeniorJD still searching answer to a [few binary changes in API between Java6 and 7](http://stackoverflow.com/questions/16219111/cant-transparent-and-undecorated-jframe-in-jdk7-when-enabling-nimbus), because Swing is in maintanace mode, then any changes are against natural rules, another issue is with modifiers in mouse events – mKorbel Sep 20 '13 at 21:41

3 Answers3

4

This is bug in Oracle JDK 7 (it cannot be reproduced in Open JDK 7 by the way).

To fix this you can make a workaround (yes, this is just a workaround, there is no guarantees that it won't break with some Java update) so that the window created for popup-menu will become non-opaque as soon as it shows up, then it will be displayed properly. Atleast for now. Here is how this can be done for Java version 7 and later:

PropertyChangeListener propertyChangeListener = new PropertyChangeListener ()
{
    @Override
    public void propertyChange ( final PropertyChangeEvent evt )
    {
        if ( evt.getNewValue () == Boolean.TRUE )
        {
            // Retrieving popup menu window (we won't find it if it is inside of parent frame)
            final Window ancestor = getWindowAncestor ( popupMenu );
            if ( ancestor != null && ancestor.getClass ().getCanonicalName ().endsWith ( "HeavyWeightWindow" ) )
            {
                // Checking that parent window for our window is opaque, only then setting opacity
                final Component parent = ancestor.getParent ();
                if ( parent != null && parent instanceof Window && parent.getBackground ().getAlpha () == 0 )
                {
                    // Making popup menu window non-opaque
                    ancestor.setBackground ( new Color ( 0, 0, 0, 0 ) );
                }
            }
        }
    }

    private Window getWindowAncestor ( Component component )
    {
        if ( component == null )
        {
            return null;
        }
        if ( component instanceof Window )
        {
            return ( Window ) component;
        }
        for ( Container p = component.getParent (); p != null; p = p.getParent () )
        {
            if ( p instanceof Window )
            {
                return ( Window ) p;
            }
        }
        return null;
    }
};
popupMenu.addPropertyChangeListener ( "visible", propertyChangeListener );

You will have to put some more effort if you also want to support JDK 6- versions because that code won't even compile on earlier JDK versions (there are no "set/getBackground" methods in Window in earlier versions).

Mikle Garin
  • 10,083
  • 37
  • 59
  • GREAT it works for me, but checking *parent.getBackground ().getAlpha () == 0* is not right as well. Better to check for `alpha < 255`. Anyway, thanks a lot! – SeniorJD Sep 20 '13 at 14:14
  • Depends on what is your target there. But its indeed better to check for < 255 in this case. – Mikle Garin Sep 20 '13 at 14:16
  • JPopup and XxxBasicPopup should be pack() before visible – mKorbel Sep 20 '13 at 19:02
  • @mKorbel no matter, in this SSCCE. seems like you had a hard day :) – SeniorJD Sep 20 '13 at 23:27
  • @SeniorJD I have three folders with diff Bugs 1. official, 2. ignored, 3. wrong codding practicies, that I'm simple tried if returns true/false – mKorbel Sep 21 '13 at 08:36
1

.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.Painter;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class PopupTest {

    private JFrame frame = new JFrame();
    private JPanel panel = new JPanel(new BorderLayout()) {
        private static final long serialVersionUID = 1L;

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(500, 500);
        }
    };
    private JButton button = new JButton("Close me");

    public PopupTest() {
        //panel.setOpaque(false);        
        panel.setBorder(BorderFactory.createLineBorder(Color.RED));
        panel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON3) {
                    JPopupMenu menu = new JPopupMenu();
                    for (int i = 0; i < 56; i++) {//only FHD display
                        menu.add(String.valueOf(i));
                    }
                    menu.show(panel, e.getX(), e.getY());
                }
            }
        });
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });
        button.setOpaque(false);
        panel.add(button, BorderLayout.SOUTH);
        frame.setLocation(150, 150);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //frame.add(panel);
        frame.setContentPane(panel);
        frame.setUndecorated(true);
        frame.pack();
        frame.setBackground(new Color(150, 50, 50, 200));
        frame.setVisible(true);
    }

    public static void main(String[] a) {
        try {
            for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(laf.getName())) {
                    UIManager.setLookAndFeel(laf.getClassName());
                    UIManager.getLookAndFeelDefaults().put("PopupMenu[Enabled].backgroundPainter",
                            new FillPainter(new Color(127, 255, 191)));
                    UIManager.getLookAndFeelDefaults().put("text", new Color(255, 0, 0));
                    //UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
                    //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new PopupTest();
            }
        });
    }
}

class FillPainter implements Painter<JComponent> {

    private final Color color;

    FillPainter(Color c) {
        color = c;
    }

    @Override
    public void paint(Graphics2D g, JComponent object, int width, int height) {
        g.setColor(color);
        g.fillRect(0, 0, width - 1, height - 1);
    }
}
Community
  • 1
  • 1
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • I will pretend that I've never saw this comment, sure here is everything possible, I put there another bugs (3k testing code lines collected from this forum) together to one JPanel, but important is effort for production code (note I'm not coder, just java fan, admin for Win & Solaris OS, then just my helicopter view, required effort for success, blocked with my endless lazyness:-) – mKorbel Sep 21 '13 at 08:45
0

Thanks Mikle Garin for a great solution, you helped me a lot to solve a similar problem! I would like to share my solution - based on Mikle's - with a little difference, that was significant in my case.

What I was looking for: transparent undecorated Windows behind JPopupMenu (my custom popups are shown with fancy speech-balloon border, so Window behind it should be invisible).

One thing that didn't work well enough with PropertyChangeListener: window appearance gets adjusted AFTER the window is shown on the screen. On Mac OS X 10.10 with java 8 window behind the popup is first shown with a white background and a thin border (L&F default), and gets adjusted a while (about 0.1 - 0.3 sec) later. Pretty annoying.

After looking a while where to place adjustment code I came up with the following simple solution: adjust window RIGHT AFTER the menu is added to the window and BEFORE showing the window. Following sample shows how to extend JPopupMenu to achieve this:

import java.awt.Color;
import java.awt.Window;
import javax.swing.JPopupMenu;
import javax.swing.Popup;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.plaf.PopupMenuUI;

public class MyPopup extends JPopupMenu
{
    public MyPopup()
    {
        super();
        //...
        setUI(new MyPopupMenuUI());
    }

    //...

    private void adjustHeavyWeightWindowIfThereIsOne()
    {
        // Retrieve popup menu window
        // on Windows we won't find it if this popup is inside of parent frame
        // on Mac we may find it even when DefaultLightWeightPopupEnabled is set to true
        final Window ancestor = SwingUtilities.getWindowAncestor(MyPopup.this);
        if (ancestor != null && ancestor.getClass().getCanonicalName().endsWith("HeavyWeightWindow"))
        {
            adjustWindowAppearance(ancestor);
        }
    }

    private void adjustWindowAppearance(Window w)
    {
        w.setBackground(new Color(0, 0, 0, 0));
        if (w instanceof RootPaneContainer)
        {
            ((RootPaneContainer)w).getRootPane().setBorder(null);
            ((RootPaneContainer)w).getRootPane().setBackground(new Color(0, 0, 0, 0));
        }
    }

    class MyPopupMenuUI extends PopupMenuUI
    {
        public Popup getPopup(JPopupMenu popup, int x, int y)
        {
            Popup toreturn = super.getPopup(popup, x, y);
            adjustHeavyWeightWindowIfThereIsOne();
            return toreturn;
        }
    }
}

Actually extending JPopupMenu is not necessary, but setting a custom PopupMenuUI that triggers an adjustment is essential. Window adjustment code can be easily modified to meet particular needs, and can be moved to custom PopupMenuUI.

jarlh
  • 42,561
  • 8
  • 45
  • 63