Can I get native-looking JPopupMenu appearance for Swing on Mac OS X?

640 Views Asked by At

I'm trying to make a combo box in Swing (under Java 7) look like a native combo box. It turns out that a JPopupMenu is used to display the options of the combo box, so it turns into a matter of making a JPopupMenu look native enough.

If I use the default Aqua look and feel, I get square edges, which is not right at all, so we'll throw that idea away right off the bat. So of course, one turns to Quaqua, which is supposed to fix this sort of thing.

public class PopupMenuTest implements Runnable {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new PopupMenuTest());
    }

    @Override
    public void run() {
        try {
            UIManager.setLookAndFeel(QuaquaManager.getLookAndFeel());
            // Uncomment this for the second example
            //PopupFactory.setSharedInstance(new CustomisedScreenPopupFactory());
        } catch (Exception ignore) {}

        JComboBox<String> comboBox = new JComboBox<>();
        comboBox.setModel(new DefaultComboBoxModel<>(
            new String[] { "One", "Two", "Three" }));

        JFrame frame = new JFrame("Test");
        frame.setLayout(new FlowLayout());
        frame.add(comboBox);
        frame.setSize(300, 200);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

This gives me a popup menu which does have rounded corners but has filled in the corners with black.

Where the code is commented out above, I tried jamming in a custom screen popup factory.

public class CustomisedScreenPopupFactory extends PopupFactory {
    private final PopupFactory delegate;

    public CustomisedScreenPopupFactory() {
        PopupFactory delegate;
        try {
            Class<? extends PopupFactory> clazz =
                Class.forName("com.apple.laf.ScreenPopupFactory")
                    .asSubclass(PopupFactory.class);
            Constructor<? extends PopupFactory> constructor =
                clazz.getDeclaredConstructor();
            constructor.setAccessible(true); // hacks
            delegate = constructor.newInstance();
        } catch (Exception ignore) {
            delegate = new PopupFactory(); // has to be set to something
        }
        this.delegate = delegate;
    }

    @Override
    public Popup getPopup(Component owner, Component contents, int x, int y) {
        Popup popup = delegate.getPopup(owner, contents, x, y);

        try {
            Method method = Popup.class.getDeclaredMethod("getComponent");
            method.setAccessible(true);
            Component component = (Component) method.invoke(popup);
            if (component instanceof JWindow) {    // always is, so far
                JWindow window = (JWindow) component;
                JRootPane rootPane = window.getRootPane();

                // This call here is what all the rest of the boilerplate was
                // added in order to access.
                AWTUtilities.setWindowOpaque(window, false);
            }
        } catch (Exception e) {
            Logger.getLogger(getClass()).error("Couldn't customise the popup window", e);
        }

        return popup;
    }
}

This gives me an interesting result where the black corners are gone, but so is the shadow.

Problem is, I want both the rounded corners and the shadow. Is it possible to get both?

Incidentally, I notice that IDEA does get it right, but I couldn't figure out from their source why it would work, so I wonder if it's because it's running on Java 6 and not Java 7...

1

There are 1 best solutions below

0
On

You can match the system's look and feel like this:

    try{
    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName);
    catch(Exception e){}

that just gets the system L&F, which would be more original than an external L&F anyways.

hope this helps!