Package de.ailis.xadrian.utils

Source Code of de.ailis.xadrian.utils.SwingUtils

/*
* Copyright (C) 2010-2012 Klaus Reimer <k@ailis.de>
* See LICENSE.TXT for licensing information.
*/

package de.ailis.xadrian.utils;

import java.awt.Desktop;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Locale;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JSpinner.DefaultEditor;
import javax.swing.KeyStroke;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.text.JTextComponent;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.WString;

import de.ailis.xadrian.support.Config;

/**
* Static utility methods for common Swing tasks.
*
* @author Klaus Reimer (k@ailis.de)
*/
public final class SwingUtils
{
    /** The logger. */
    private static final Log LOG = LogFactory.getLog(SwingUtils.class);

    /** If platform has a shell32 library. */
    private static boolean hasShell32;

    static
    {
        try
        {
            Native.register("shell32");
            hasShell32 = true;
        }
        catch (final Throwable e)
        {
            hasShell32 = false;
        }
    }

    /**
     * Private constructor to prevent instantiation
     */
    private SwingUtils()
    {
        // Empty
    }

    /**
     * Gives a component a popup menu
     *
     * @param component
     *            The target component
     * @param popup
     *            The popup menu
     */
    public static void setPopupMenu(final JComponent component,
        final JPopupMenu popup)
    {
        component.addMouseListener(new MouseAdapter()
        {
            @Override
            public void mousePressed(final MouseEvent e)
            {
                // Ignore mouse buttons outside of the normal range. This
                // fixes problems with trackpad scrolling.
                if (e.getButton() > MouseEvent.BUTTON3) return;

                if (e.isPopupTrigger())
                {
                    popup.show(component, e.getX(), e.getY());
                }
            }

            @Override
            public void mouseReleased(final MouseEvent e)
            {
                // Ignore mouse buttons outside of the normal range. This
                // fixes problems with trackpad scrolling.
                if (e.getButton() > MouseEvent.BUTTON3) return;

                if (e.isPopupTrigger())
                {
                    popup.show(component, e.getX(), e.getY());
                }
            }
        });
    }

    /**
     * Called internally by installGtkPopupBugWorkaround to fix the thickness
     * of a GTK style field by setting it to a minimum value of 1.
     *
     * @param style
     *            The GTK style object.
     * @param fieldName
     *            The field name.
     * @throws Exception
     *             When reflection fails.
     */
    private static void fixGtkThickness(Object style, String fieldName)
        throws Exception
    {
        Field field = style.getClass().getDeclaredField(fieldName);
        boolean accessible = field.isAccessible();
        field.setAccessible(true);
        field.setInt(style, Math.max(1, field.getInt(style)));
        field.setAccessible(accessible);
    }

    /**
     * Called internally by installGtkPopupBugWorkaround. Returns a specific
     * GTK style object.
     *
     * @param styleFactory
     *            The GTK style factory.
     * @param component
     *            The target component of the style.
     * @param regionName
     *            The name of the target region of the style.
     * @return The GTK style.
     * @throws Exception
     *             When reflection fails.
     */
    private static Object getGtkStyle(Object styleFactory,
        JComponent component, String regionName) throws Exception
    {
        // Create the region object
        Class<?> regionClass = Class.forName("javax.swing.plaf.synth.Region");
        Field field = regionClass.getField(regionName);
        Object region = field.get(regionClass);

        // Get and return the style
        Class<?> styleFactoryClass = styleFactory.getClass();
        Method method = styleFactoryClass.getMethod("getStyle",
            new Class<?>[] { JComponent.class, regionClass });
        boolean accessible = method.isAccessible();
        method.setAccessible(true);
        Object style = method.invoke(styleFactory, component, region);
        method.setAccessible(accessible);
        return style;
    }

    /**
     * Swing menus are looking pretty bad on Linux when the GTK LaF is used (See
     * bug #6925412). It will most likely never be fixed anytime soon so this
     * method provides a workaround for it. It uses reflection to change the GTK
     * style objects of Swing so popup menu borders have a minimum thickness of
     * 1 and menu separators have a minimum vertical thickness of 1.
     */
    public static void installGtkPopupBugWorkaround()
    {
        // Get current look-and-feel implementation class
        LookAndFeel laf = UIManager.getLookAndFeel();
        Class<?> lafClass = laf.getClass();

        // Do nothing when not using the problematic LaF
        if (!lafClass.getName().equals(
            "com.sun.java.swing.plaf.gtk.GTKLookAndFeel")) return;

        // We do reflection from here on. Failure is silently ignored. The
        // workaround is simply not installed when something goes wrong here
        try
        {
            // Access the GTK style factory
            Field field = lafClass.getDeclaredField("styleFactory");
            boolean accessible = field.isAccessible();
            field.setAccessible(true);
            Object styleFactory = field.get(laf);
            field.setAccessible(accessible);

            // Fix the horizontal and vertical thickness of popup menu style
            Object style = getGtkStyle(styleFactory, new JPopupMenu(),
                "POPUP_MENU");
            fixGtkThickness(style, "yThickness");
            fixGtkThickness(style, "xThickness");

            // Fix the vertical thickness of the popup menu separator style
            style = getGtkStyle(styleFactory, new JSeparator(),
                "POPUP_MENU_SEPARATOR");
            fixGtkThickness(style, "yThickness");
        }
        catch (Exception e)
        {
            // Silently ignored. Workaround can't be applied.
        }
    }

    /**
     * Installs a workaround for bug #4699955 in a JSpinner.
     *
     * @param spinner
     *            The spinner to fix
     */
    public static void installSpinnerBugWorkaround(final JSpinner spinner)
    {
        ((DefaultEditor) spinner.getEditor()).getTextField().addFocusListener(
            new FocusAdapter()
            {
                @Override
                public void focusGained(final FocusEvent e)
                {
                    if (e.getSource() instanceof JTextComponent)
                    {
                        final JTextComponent text =
                            ((JTextComponent) e.getSource());
                        SwingUtilities.invokeLater(new Runnable()
                        {
                            @Override
                            public void run()
                            {
                                text.selectAll();
                            }
                        });
                    }
                }
            });
        spinner.addFocusListener(new FocusAdapter()
        {
            @Override
            public void focusGained(final FocusEvent e)
            {
                if (e.getSource() instanceof JSpinner)
                {
                    final JTextComponent text =
                        ((DefaultEditor) ((JSpinner) e.getSource())
                            .getEditor()).getTextField();
                    SwingUtilities.invokeLater(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            text.requestFocus();
                        }
                    });
                }
            }
        });
    }

    /**
     * Checks if the specified window (may it be a dialog or a frame) is
     * resizable.
     *
     * @param window
     *            The window
     * @return True if window is resizable, false if not
     */
    public static boolean isResizable(final Window window)
    {
        if (window instanceof Dialog) return ((Dialog) window).isResizable();
        if (window instanceof Frame) return ((Frame) window).isResizable();
        return false;
    }

    /**
     * Prepares the locale. The default is the system locale.
     */
    public static void prepareLocale()
    {
        final String locale = Config.getInstance().getLocale();
        if (locale != null) Locale.setDefault(new Locale(locale));
    }

    /**
     * Prepares the theme. The theme can be overridden with the environment
     * variable XADRIAN_SYSTHEME. The default is the system look and feel.
     */
    public static void prepareTheme()
    {
        final String theme = Config.getInstance().getTheme();
        if (theme != null)
        {
            try
            {
                UIManager.setLookAndFeel(theme);
                installGtkPopupBugWorkaround();
                return;
            }
            catch (final Exception e)
            {
                LOG.warn("Can't set theme " + theme +
                    ". Falling back to system look-and-feel. Reason: " + e, e);
            }
        }

        try
        {
            UIManager
                .setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            installGtkPopupBugWorkaround();
        }
        catch (final Exception e)
        {
            LOG.warn("Can't set system look-and-feel. " +
                "Falling back to default. Reason: " + e, e);
        }
    }

    /**
     * Prepares the Swing GUI.
     */
    public static void prepareGUI()
    {
        prepareLocale();
        prepareTheme();
    }

    /**
     * Runs the specified component in an empty test frame. This method is used
     * to test single components during development.
     *
     * @param component
     *            The component to test
     * @throws Exception
     *             When something goes wrong
     */
    public static void testComponent(final JComponent component)
        throws Exception
    {
        final JFrame frame = new JFrame("Test");
        frame.setName("componentTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(component);
        frame.pack();
        frame.setVisible(true);
    }

    /**
     * Sets the preferred height of the specified component.
     *
     * @param component
     *            The component
     * @param height
     *            The preferred height
     */
    public static void setPreferredHeight(final JComponent component,
        final int height)
    {
        component.setPreferredSize(new Dimension(
            component.getPreferredSize().width, height));
    }

    /**
     * Sets the preferred width of the specified component.
     *
     * @param component
     *            The component
     * @param width
     *            The preferred width
     */
    public static void setPreferredWidth(final JComponent component,
        final int width)
    {
        component.setPreferredSize(new Dimension(width, component
            .getPreferredSize().height));
    }

    /**
     * Adds a component action.
     *
     * @param component
     *            The compoenet to add the action to
     * @param action
     *            The action to add
     */
    public static void addComponentAction(final JComponent component,
        final Action action)
    {
        final InputMap imap =
            component.getInputMap(component.isFocusable()
                ? JComponent.WHEN_FOCUSED : JComponent.WHEN_IN_FOCUSED_WINDOW);
        final ActionMap amap = component.getActionMap();
        final KeyStroke ks =
            (KeyStroke) action.getValue(Action.ACCELERATOR_KEY);
        imap.put(ks, action.getValue(Action.NAME));
        amap.put(action.getValue(Action.NAME), action);
    }

    /**
     * Opens a URL in the browser. It first tries to do this with the Desktop
     * API. If this fails then it tries to use the FreeDesktop-API.
     *
     * @param uri
     *            The URI to open.
     */
    public static void openBrowser(final URI uri)
    {
        try
        {
            try
            {
                Desktop.getDesktop().browse(uri);
            }
            catch (final UnsupportedOperationException e)
            {
                Runtime.getRuntime().exec("xdg-open '" + uri + "'");
            }
        }
        catch (final IOException e)
        {
            LOG.error("Unable to external browser: " + e, e);
        }
    }

    /**
     * Opens a URL in the browser. It first tries to do this with the Desktop
     * API. If this fails then it tries to use the FreeDesktop-API.
     *
     * @param url
     *            The URL to open.
     */
    public static void openBrowser(final String url)
    {
        try
        {
            openBrowser(new URI(url));
        }
        catch (final URISyntaxException e)
        {
            LOG.error(e.toString(), e);
        }
    }

    /**
     * Sets the application name. There is no API for this (See
     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6528430) so this
     * method uses reflection to do this. This may fail if the Java
     * implementation is changed but any exception here will be ignored.
     *
     * The application name is currently only used for X11 desktops and only
     * important for some window managers like Gnome Shell.
     *
     * @param appName
     *            The application name to set.
     */
    public static void setAppName(final String appName)
    {
        final Toolkit toolkit = Toolkit.getDefaultToolkit();
        final Class<?> cls = toolkit.getClass();

        try
        {
            // When X11 toolkit is used then set the awtAppClassName field
            if (cls.getName().equals("sun.awt.X11.XToolkit"))
            {
                final Field field = cls.getDeclaredField("awtAppClassName");
                field.setAccessible(true);
                field.set(toolkit, appName);
            }
        }
        catch (final Exception e)
        {
            LOG.warn("Unable to set application name: " + e, e);
        }
    }

    /**
     * Sets the app user model id. This is needed for the Windows 7 taskbar
     * so the application is correctly associated with the starter icon.
     * The same app user model id must be set in the shortcut.
     *
     * @param appId
     *            The app user model id to set.
     */
    public static void setAppUserModelId(final String appId)
    {
        if (!hasShell32) return;
        try
        {
            final long errorCode =
                SetCurrentProcessExplicitAppUserModelID(new WString(appId))
                    .longValue();
            if (errorCode != 0)
                LOG.error("Unable to set appUserModelID. Error code " +
                    errorCode);
        }
        catch (final Throwable e)
        {
            LOG.error("Unable to set appUserModelID: " + e, e);
        }
    }

    /**
     * Native Windows function mapped via JNA.
     *
     * @param appId
     *            The app user model ID to set.
     * @return Error code.
     */
    private static native NativeLong SetCurrentProcessExplicitAppUserModelID(
        WString appId);
}
TOP

Related Classes of de.ailis.xadrian.utils.SwingUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.