
package uk.co.wingpath.gui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import uk.co.wingpath.util.*;

public class Gui
{
    static
    {
        // Try to check that we have an X-server (if under Linux), and
        // display a sensible error message instead of a stack-trace if not.
        try
        {
            UIManager.getColor ("TextField.background");
        }
        catch (Throwable e)
        {
            // Presumably no X-server.
            System.err.println (e.getMessage ());
            System.err.println ("You need to have an X-server running.");
            System.exit (23);
        }
    }

    public static final Color COLOUR_BACKGROUND_ERROR =
        new Color (255, 200, 200);
    public static final Color COLOUR_BACKGROUND_UNAPPLIED =
        new Color (255, 255, 100);
    public static final Color COLOUR_BACKGROUND_NORMAL =
        UIManager.getColor ("TextField.background");
    public static final Color COLOUR_BACKGROUND_INACTIVE =
        UIManager.getColor ("TextField.inactiveBackground");
    public static final Color COLOUR_BACKGROUND_SELECTED =
        UIManager.getColor ("TextField.selectionBackground");
    public static final Color COLOUR_BACKGROUND_PANEL =
        UIManager.getColor ("Panel.background");

    public static void addShortCut (JComponent component, String name,
        int key, int modifiers,
        Action action)
    {
        InputMap inputMap = component.getInputMap (
            JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        inputMap.put (KeyStroke.getKeyStroke (key, modifiers), name);
        component.getActionMap ().put (name, action);
    }

    public static void addHelpShortCut (JComponent component, Action action)
    {
        addShortCut (component, "help", KeyEvent.VK_F1, 0, action);
    }

    public static Action toolTipsAction = new AbstractAction ("Tool tips")
        {
            public void actionPerformed (ActionEvent e)
            {
                ToolTipManager tm = ToolTipManager.sharedInstance ();
                tm.setEnabled (!tm.isEnabled ());
            }
        };

    static
    {
        toolTipsAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Enable display of tooltips");
    }

    public static void addEnableToolTipShortCut (JComponent component)
    {
        addShortCut (component, "tooltip", KeyEvent.VK_F1,
            InputEvent.SHIFT_MASK, toolTipsAction);
    }

    public static void setEnabled (JComponent component, boolean enabled)
    {
        component.setEnabled (enabled);
        component.setFocusable (enabled);
    }

    private static class WListCellRenderer
        implements ListCellRenderer
    {
        private ListCellRenderer renderer;
        private String [] tooltips;

        WListCellRenderer (ListCellRenderer renderer, String [] tooltips)
        {
            this.renderer = renderer;
            this.tooltips = tooltips.clone ();
        }

        public Component getListCellRendererComponent (JList list,
            Object value, int index, boolean isSelected, boolean cellHasFocus)
        {
            JComponent comp =
                (JComponent) renderer.getListCellRendererComponent (
                    list, value, index, isSelected, cellHasFocus);
            if (index >= 0 && index < tooltips.length)
                comp.setToolTipText (tooltips [index]);
            return comp;
        }
    }

    public static void setToolTipText (JComboBox comboBox, String [] tooltips)
    {
        ListCellRenderer renderer = comboBox.getRenderer ();
        comboBox.setRenderer (new WListCellRenderer (renderer, tooltips));
    }

    public static void setToolTipText (JList list, String [] tooltips)
    {
        ListCellRenderer renderer = list.getCellRenderer ();
        list.setCellRenderer (new WListCellRenderer (renderer, tooltips));
    }

    public static JLabel createDialogHeading (String heading, int fontSize)
    {
        JLabel label = new JLabel (heading);
        label.setFont (new Font ("SansSerif", Font.BOLD, fontSize));
        label.setHorizontalAlignment (SwingConstants.CENTER);
        label.setAlignmentX (Component.CENTER_ALIGNMENT);
        label.setBorder (BorderFactory.createEmptyBorder (3, 0, 5, 0));
        return label;
    }

    public static JLabel createDialogHeading (String heading)
    {
        return createDialogHeading (heading, 16);
    }

    /**
    * Sets the application class name.
    * <p>This name is used by the XToolKit on Unix/Linux systems to set the
    * WM_CLASS X property, which is displayed by some window managers (e.g. by
    * xfce window manager when you use Alt-Tab to step through windows).
    * The default value is derived from the fully-qualified name of the
    * top-level class of the program. E.g. for Modsak, the default is
    * "Uk co wingpath modsak Bootstrap"!
    * <p>The code for this method comes from
    * http://elliotth.blogspot.com/2007_02_01_archive.html
    * and relies on reflection to set a private field in the toolkit
    * (sun.awt.X11.XToolkit.awtAppClassName).
    * This is obviously vulnerable to implementation changes.
    * @param name the new application class name
    */
    public static void setAppClassName (String name)
    {
        if (Installer.getJavaVersion () < 9)
        {
            try
            {
                Toolkit xToolkit = Toolkit.getDefaultToolkit();
                java.lang.reflect.Field awtAppClassNameField =
                    xToolkit.getClass().getDeclaredField("awtAppClassName");
                awtAppClassNameField.setAccessible(true);
                awtAppClassNameField.set(xToolkit, name);
            }
            catch (Exception e)
            {
                // System.out.println (e.getMessage ());
            }
        }
    }

    /**
    * Returns the width (in pixels) required to display the specified text in
    * the specified component.
    * The width is obtained from the font used by the component, and
    * includes the component insets.
    * @param text the text whose width is required.
    * @param component component to get font information from. This should
    * be the same kind of component as the one that is used to display the
    * text, but does not need to be the actual component (e.g. you could
    * pass 'new JLabel ()' if you are going to display the text in a JLabel).
    * @return width of text in pixels.
    */
    public static int getTextWidth (String text, JComponent component)
    {
        Insets insets = component.getInsets ();
        FontMetrics metrics = component.getFontMetrics (component.getFont());
        int width = metrics.stringWidth (text);
        return width + insets.left + insets.right;
    }

    /**
    * Returns the width (in pixels) required to display the specified text.
    * The width is obtained from the font used by {@code JTextField}s, and
    * includes the {@code JTextField} insets.
    * @param text the text whose width is required.
    * @return width of text in pixels.
    */
    public static int getTextWidth (String text)
    {
        return getTextWidth (text, new JTextField ());
    }

    /**
    * Returns the width (in pixels) required to display the specified number
    * of characters.
    * <p>This uses the width of the character '0' in the font used by the
    * JComponent plus any insets.
    * @param count the number of characters whose width is required.
    * @param component component to get font information from. This should
    * be the same kind of component as the one that is used to display the
    * text, but does not need to be the actual component (e.g. you could
    * pass 'new JLabel ()' if you are going to display the text in a JLabel).
    * @return width of text in pixels.
    */
    public static int getTextWidth (int count, JComponent component)
    {
        Insets insets = component.getInsets ();
        FontMetrics metrics = component.getFontMetrics (component.getFont());
        int width = metrics.charWidth ('0');
        return width * count + insets.left + insets.right;
    }

    /**
    * Returns the width (in pixels) required to display the specified number
    * of characters.
    * <p>This uses the width of the character '0' in the font used by {@code
    * JTextField} plus any insets.
    * @param count the number of characters whose width is required.
    * @return width of text in pixels.
    */
    public static int getTextWidth (int count)
    {
        return getTextWidth (count, new JTextField ());
    }

    /**
    * Inserts HTML line breaks into a string to limit the width.
    * @param str the string to be broken into multiple lines.
    * @param maxWidth the maximum line width in pixels.
    * @param component component to get font information from. This should
    * be the same kind of component as the one that is used to display the
    * string, but does not need to be the actual component (e.g. you could
    * pass 'new JLabel ()' if you are going to display the string in a JLabel).
    * @return the string with HTML markup added to split it into multiple lines.
    */
    public static String breakString (String str, int maxWidth,
        JComponent component)
    {
        String [] words = str.split (" ");

        for (int i = 0 ; i < words.length ; ++i)
        {
            int wordWidth = getTextWidth (words [i], component);
            if (wordWidth > maxWidth)
                maxWidth = wordWidth;
        }

        String result = "<html>";
        String line = "";

        for (int i = 0 ; i < words.length ; ++i)
        {
            String word = words [i];
            if (i != 0)
            {
                int width = getTextWidth (line + " " + word, component);
                if (width > maxWidth)
                {
                    result += "<br>";
                    line = "";
                }
                else
                {
                    result += " ";
                    line += " ";
                }
            }
            result += word;
            line += word;
        }

        return result;
    }

    /**
    * Selects the text to be used in a tooltip.
    * <p>If the text that is displayed in a text field or table cell is too
    * long to be fully displayed, this method returns the text so that it
    * can be displayed in a tooltip. Otherwise, the normal tooltip text is
    * returned.
    * @param tooltip the normal tooltip text, which may be {@code null}.
    * @param text the text displayed in the text field or table cell.
    * @param width the width in pixels of the text field or table cell.
    * @return the text to be displayed as a tooltip.
    */
    public static String selectToolTipText (String tooltip, String text,
        int width)
    {
        // If this method is called before the field has been laid out, the
        // width will be 0.
        JToolTip toolTip = new JToolTip ();
        if (width != 0 && getTextWidth (text, toolTip) > width)
        {
            return breakString (text, getTextWidth (40, toolTip), toolTip);
        }
        return tooltip;
    }
}

