javawindowsmacosswinguser-interface

Difference in GUI in Windows and macOS


I'm a Windows user, and I have developed a small program to calculate a few parameter based on some inputs. The GUI in Windows is displayed as expected, but in macOS it's different. Please let me know how to correct it. The code for the same is.

package home;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SwingConstants;

import java.awt.Component;
import java.awt.ComponentOrientation;

/**
 * Home Page for the application.
 * @author Harshit Rathore
 * 
 */
public class Home_Main {

    private JFrame f;
    private JXTabbedPane tabbedpane;
    private ImageIcon icon_view, icon_analysis, icon_algo, icon_info, icon_sche_gen;
    Schedule_Generation schedule_gen;
    View_JPanel2D view2d;
    View_JPanel3D view3d;
    MapJPanel mapPanel;

    Home_Main() {

        f = new JFrame();
        f.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
        f.setTitle("Anti-Accidental Algorithm");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        tabbedpane = new JXTabbedPane(JTabbedPane.LEFT);
        AbstractTabRenderer renderer = (AbstractTabRenderer)tabbedpane.getTabRenderer();
        renderer.setPrototypeText("This text is a prototype->");
        renderer.setHorizontalTextAlignment(SwingConstants.LEADING);

        icon_view = new ImageIcon(this.getClass().getResource("/view_icon.png"));
        icon_analysis = new ImageIcon(this.getClass().getResource("/analysis_icon.png"));
        icon_algo = new ImageIcon(this.getClass().getResource("/algo_icon.png"));
        icon_info = new ImageIcon(this.getClass().getResource("/info_icon.png"));
        icon_sche_gen = new ImageIcon(this.getClass().getResource("/schedule_icon.png"));

        mapPanel = new home.MapJPanel();
        view2d = new home.View_JPanel2D();
        view3d = new home.View_JPanel3D();
        schedule_gen = new home.Schedule_Generation(view2d, view3d, mapPanel);

        tabbedpane.addTab("Algorithm", icon_algo, new home.Algo_JPanel());
        tabbedpane.addTab("<html>Schedule<br>Generation</html>", icon_sche_gen, schedule_gen);
        tabbedpane.addTab("<html>View Data 2D<br>(Azimuth, Elevation)</html>", icon_view, view2d);
        tabbedpane.addTab("<html>View Data 3D<br>(Azimuth, Elevation, Tilt)</html>", icon_view, view3d);
        tabbedpane.addTab("MapPanel", icon_info, mapPanel);
        tabbedpane.addTab("<html>Windload<br>Torque</html>", icon_analysis, new home.WindloadTorque());

        f.getContentPane().add(tabbedpane);
        f.setMinimumSize(new Dimension(1100, 800));
        f.setSize(1600, 800);
        f.setVisible(true);
        f.setEnabled(true);
    }
    
    /**
     * The main function of the software.
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new Home_Main();
    }

    // custom tabbedpane

    class JXTabbedPane extends JTabbedPane {

        /**
         * 
         */
        private static final long serialVersionUID = 1L;
        private ITabRenderer tabRenderer = new DefaultTabRenderer();

        public JXTabbedPane() {
            super();
        }

        public JXTabbedPane(int tabPlacement) {
            super(tabPlacement);
        }

        public JXTabbedPane(int tabPlacement, int tabLayoutPolicy) {
            super(tabPlacement, tabLayoutPolicy);
        }

        public ITabRenderer getTabRenderer() {
            return tabRenderer;
        }

        public void setTabRenderer(ITabRenderer tabRenderer) {
            this.tabRenderer = tabRenderer;
        }

        @Override
        public void addTab(String title, Component component) {
            this.addTab(title, null, component, null);
        }

        @Override
        public void addTab(String title, Icon icon, Component component) {
            this.addTab(title, icon, component, null);
        }

        @Override
        public void addTab(String title, Icon icon, Component component, String tip) {
            super.addTab(title, icon, component, tip);
            int tabIndex = getTabCount() - 1;
            Component tab = tabRenderer.getTabRendererComponent(this, title, icon, tabIndex);
            super.setTabComponentAt(tabIndex, tab);
        }
    }

    interface ITabRenderer {

        public Component getTabRendererComponent(JTabbedPane tabbedPane, String text, Icon icon, int tabIndex);

    }

    abstract class AbstractTabRenderer implements ITabRenderer {

        private String prototypeText = "";
        private Icon prototypeIcon = new ImageIcon(this.getClass().getResource("/view_icon.png"));
        private int horizontalTextAlignment = SwingConstants.CENTER;
        private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

        public AbstractTabRenderer() {
            super();
        }

        public void setPrototypeText(String text) {
            String oldText = this.prototypeText;
            this.prototypeText = text;
            firePropertyChange("prototypeText", oldText, text);
        }

        public String getPrototypeText() {
            return prototypeText;
        }

        public Icon getPrototypeIcon() {
            return prototypeIcon;
        }

        public void setPrototypeIcon(Icon icon) {
            Icon oldIcon = this.prototypeIcon;
            this.prototypeIcon = icon;
            firePropertyChange("prototypeIcon", oldIcon, icon);
        }

        public int getHorizontalTextAlignment() {
            return horizontalTextAlignment;
        }

        public void setHorizontalTextAlignment(int horizontalTextAlignment) {
            this.horizontalTextAlignment = horizontalTextAlignment;
        }

        public PropertyChangeListener[] getPropertyChangeListeners() {
            return propertyChangeSupport.getPropertyChangeListeners();
        }

        public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
            return propertyChangeSupport.getPropertyChangeListeners(propertyName);
        }

        public void addPropertyChangeListener(PropertyChangeListener listener) {
            propertyChangeSupport.addPropertyChangeListener(listener);
        }

        public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
            propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
        }

        protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
            PropertyChangeListener[] listeners = getPropertyChangeListeners();
            for (int i = listeners.length - 1; i >= 0; i--) {
                listeners[i].propertyChange(new PropertyChangeEvent(this, propertyName, oldValue, newValue));
            }
        }
    }

    class DefaultTabRenderer extends AbstractTabRenderer implements PropertyChangeListener {

        private Component prototypeComponent;

        public DefaultTabRenderer() {
            super();
            prototypeComponent = generateRendererComponent(getPrototypeText(), getPrototypeIcon(),
                    getHorizontalTextAlignment());
            addPropertyChangeListener(this);
        }

        private Component generateRendererComponent(String text, Icon icon, int horizontalTabTextAlignmen) {
            JPanel rendererComponent = new JPanel(new GridBagLayout());
            rendererComponent.setOpaque(false);

            GridBagConstraints c = new GridBagConstraints();
            c.insets = new Insets(2, 4, 2, 4);
            c.fill = GridBagConstraints.HORIZONTAL;
            rendererComponent.add(new JLabel(icon), c);

            c.gridx = 1;
            c.weightx = 1;
            rendererComponent.add(new JLabel(text, horizontalTabTextAlignmen), c);

            return rendererComponent;
        }

        @Override
        public Component getTabRendererComponent(JTabbedPane tabbedPane, String text, Icon icon, int tabIndex) {
            Component rendererComponent = generateRendererComponent(text, icon, getHorizontalTextAlignment());
            int prototypeWidth = prototypeComponent.getPreferredSize().width;
            int prototypeHeight = prototypeComponent.getPreferredSize().height;
            rendererComponent.setPreferredSize(new Dimension(prototypeWidth, prototypeHeight));
            return rendererComponent;
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            String propertyName = evt.getPropertyName();
            if ("prototypeText".equals(propertyName) || "prototypeIcon".equals(propertyName)) {
                this.prototypeComponent = generateRendererComponent(getPrototypeText(), getPrototypeIcon(),
                        getHorizontalTextAlignment());
            }
        }
    }
}

The outputs are as follows. The first one is from Windows (desired one), and the second is from macOS (needs modification).

Windows output (desired):

Windows output (desired)

macOS output (needs modification):

macOS output (needs modification)


Solution

  • The reason for the buttons looking different is because of Java Swing's LAF (Look & Feel).

    The Look and Feel is like the master “theme” for your Swing Application where it defines specific rules on how certain components should look, etc…

    Java Swing on Windows looks different from on Unix & OSX is because there is a different provider for the LAF (known as LAF "metal")

    You thus can pack a LAF library into your program, and you can find many here. To call a theme to be used, you must place this before any GUI/Swing events are called (AKA repaints, component inits, etc.):

    try {
       UIManager.setLookAndFeel(myThemeLAF.class.getName());
    } catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
    }
    

    Alternatively, you could override the individual paintComponent(Graphics g) methods.

    This can be achieved either through anonymous classes or just having a class extend that object and override it from there:

    JPanel myPanel = new JPanel() {
       @Override
       public void paintComponent(Graphics g) {
          super.paintComponent(g);
          //... draw stuffs
       }
    };
    
    public class MyPanel extends JPanel {
       @Override
       public void paintComponent(Graphics g) {
          super.paintComponent(g);
          // paint stuffs
       }
    }