package org.jcon.ui;

import org.jcon.util.DataLib;
import java.awt.*;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.util.StringTokenizer;

import javax.swing.JButton;
import javax.swing.UIManager;
import javax.swing.JPanel;

/**
 * General visual related methods. All static methods.
 * (Comment out DataLib methods if you don't have it)
 *
 * @author Jack Harich
 */
public class VisualLib {

//---------- Public Fields -------------------------------
// false for Windows mostly, true for metal
public static boolean useCrossPlatformLookAndFeel = false;

//---------- Public Methods ------------------------------
public static void beep() {
    Toolkit.getDefaultToolkit().beep();
}
/**
 * Converts tabs to the number of spaced given by
 * spaces. This is very handy since using tabs in text
 * files are problematic. Actually I'm surprised this isn't
 * included in awt or Swing.
 */
public static void convertTabToSpaces(TextArea area, int spaces) {
    String text = area.getText();
    int index = text.indexOf("\t"); // Tab
    if (index > -1) {
        String spacer = DataLib.fillString(' ', spaces);
        text = text.substring(0, index) + spacer +
            text.substring(index + 1);
        // Save and restore caret position for usability
        int caretPosition = area.getCaretPosition();
        area.setText(text);
        area.setCaretPosition(caretPosition - 1 + spaces);
    }
}
/**
 * Installs the LookAndFeel as defined by the current
 * constant. Call this once in system startup.
 */
public static void installLookAndFeel() {
    try {
        // Could use UIManager.setLookAndFeel(
        //    "javax.swing.plaf.metal.MetalLookAndFeel");
        if (useCrossPlatformLookAndFeel) {
            UIManager.setLookAndFeel(UIManager
                .getCrossPlatformLookAndFeelClassName());
        } else {
            UIManager.setLookAndFeel(UIManager
                .getSystemLookAndFeelClassName());
        }
    } catch(Exception ex) {
        print(".installLookAndFeel() - Cannot set look and feel");
        ex.printStackTrace();
    }
}
/**
 * Returns the number of pixels required for the number of
 * columns to draw "average" text on the Component.
 * <p>
 * Useful for setting TableColumn's width in Swing.
 */
 // Note that the technique in JTextField.getColumnWidth()
 // produces about 50% too wide a result
 // This is return columns * metrics.charWidth('m');
public static int getColumnsWidth(int columns, Component comp) {
    FontMetrics metrics = comp.getToolkit().getFontMetrics(
        comp.getFont());
    String text = "AbcdefghijklmnopqrstuvqxyzAbcd"; // 30
    int textWidth = metrics.stringWidth(text);
    return (textWidth * columns) / 30;
}
/**
 * Adds the @param listener to all components in
 * comp1. This technique is more efficient than managing
 * addKeyListener to components separately.
 * Designed to be used to detect hot keys and such by
 * passing a Window as the Component. RECURSIVE
 */
public static void addKeyListenerToAll(Component comp1, KeyListener listener) {
    comp1.addKeyListener(listener);
    if (comp1 instanceof Container) {
        Component[] comps = ((Container)comp1).getComponents();
        for (int i = 0; i < comps.length; i++) {
            Component comp2 = (Component)comps[i];
            if (comp2 instanceof Container) {
                addKeyListenerToAll(comp2, listener);
            } else {
                comp2.addKeyListener(listener);
            }
        }
    }
}
/**
 * Creates and returns a multi-line Panel containing the
 * message. This is automatically line wrapped.
 * Note - Will improve this to use Swing. ***
 */
public static JPanel createMultiLinePanel(String message) {
    message = checkForExcessLineLength(message, 0);
    JPanel mainPanel = new JPanel();
    GridBagLayout gbLayout = new GridBagLayout();
    mainPanel.setLayout(gbLayout);
    GridBagLib.addMultilineString(message, mainPanel);
    return mainPanel;
}

/**
 * Checks the text for excess length per line, returning
 * the checked String. If max is
 * exceeded on a line, it attempts to insert a "\n" at an
 * appropriate place at the first space or comma found
 * searching backward. We do not wrap on periods since that
 * datum should probably be unbroken.
 * <p>
 * A reasonable max is 100. A max of zero will use the
 * default, which is currently 100. For VGA 100 is fine,
 * snce the max line length almost fills the screen width.
 * Later zero will cause the max to be calculated as 80%
 * of the screen width. ***
 */
public static String checkForExcessLineLength(String text,
        int max) {
    if (max == 0) max = 100;
    StringBuffer newText = new StringBuffer(100);
    StringTokenizer lines = new StringTokenizer(text, "\n", true);
    while (lines.hasMoreTokens()) {
        String line = lines.nextToken();
        if (line.length() <= max) {
            newText.append(line);
        } else {
            wrapLine(line, newText, max);
        }
    }
    // Remove last "\n" as returning
    String finalText = newText.toString();
    return finalText.substring(0, finalText.length());
}
// line.length() is > max
private static void wrapLine(String line,
        StringBuffer newText, int max) {

    while (line.length() > max) {
        for (int i = max - 1; i >= 0; i--) {
            char letter = line.charAt(i);
            if (letter == ' ' || letter == ',') {
                 // Wrap
                 String prefix = line.substring(0, i + 1);
                 newText.append(prefix + "\n");
                 line = line.substring(i + 1);
                 break;
            }
        }
    }
    newText.append(line);
}
/**
 * Finds and returns the top Container for the component.
 * Returns null if none. Useful for calling validate() on
 * the top component to force a layout when a component's
 * size or presence has changed dynamically. Note - This 
 * was before Swing. See SwingUtilities for better utilities
 * like this.
 */
public static Container findTopContainer(Component component) {
    Container parent = component.getParent();
    while (parent != null) {
        //print(" - parent = " + parent.getClass().getName());
        if (parent.getParent() == null) {
            return parent;
        } else {
            parent = parent.getParent();
        }
    }
    return parent;
}
/**
 * Converts the line to Insets. The line must be of the
 * format "top, left, bottom, right" where each quantity
 * is an integer.
 */
public static Insets parseInsets(String line) {
    String[] args = DataLib.convertDelimStringToArray(line, ", ");

    return new Insets(Integer.parseInt(args[0]),
                      Integer.parseInt(args[1]),
                      Integer.parseInt(args[2]),
                      Integer.parseInt(args[3]) );
}
/**
 * Converts the line to a Dimension. The line must be of
 * the  format "width, height" where each quantity
 * is an integer.
 */
public static Dimension parseDimension(String line) {
    String[] args = DataLib.convertDelimStringToArray(line, ", ");

    return new Dimension(Integer.parseInt(args[0]),
                         Integer.parseInt(args[1]) );
}
/**
 * Returns a new Font like the component's font in either
 * bold or plain style. This can be used to set a component's
 * font to bold or plain easily.
 */
public static Font createBoldFont(Component comp, boolean isBold) {
    Font oldFont = comp.getFont();
    int style = (isBold ? Font.BOLD : Font.PLAIN);
    return new Font(oldFont.getName(),
        style, oldFont.getSize());
}
/**
 * Returns a bold version of the given font. Useful for
 * changing font from plain to bold. This is a workaround
 * for the fact that Fonts are immutable.
 */
public static Font makeFontBold(Font font) {
    return new Font(font.getName(), Font.BOLD, font.getSize());
}
/**
 * Returns a new Font like the component's font except the
 * font's point size is increased by extraPointSize.
 * This is handy for enlarging fonts. A negative value
 * will decrease the point size. Zero will have no effect.
 */
public static Font createLargerFont(Component comp, 
        int extraPointSize) {
    Font oldFont = comp.getFont();
    return new Font(oldFont.getName(),
        oldFont.getStyle(), oldFont.getSize() + extraPointSize);
}
public static Font createLargerFont(Font font, int extraPointSize) {
    return new Font(font.getName(),
        font.getStyle(), font.getSize() + extraPointSize);
}
/**
* Creates and returns a JButton using the text, command and
* toolTip. This button differs from the normal by having
* smaller left and right blank space, allowing it to be
* more compact. 
* <p>
* @param text     the button label.
* @param command  the action command name.
* @param toolTip  the tool tip text.
*/
public static JButton createCompactButton(String text,
        String command, String toolTip) {
            
    JButton button = new JButton(text);
    if (toolTip != null) button.setToolTipText(toolTip);
    button.setActionCommand(command);
    
    // Remove excess left and right space
    // Left and right were 14, top is 2
    Insets margin = button.getMargin();
    margin.left = margin.top + 1;   
    margin.right = margin.top + 2;
    
    // Fix wimpy problem with Swing "A"
    if (text != null && text.equals("A")) {
        margin.left += 4;
        margin.right += 4;
    }
    button.setMargin(margin);
       
    return button;    
}    
/**
* Returns true if the event is a right click, false if not.
* We call a spade a spade here, rather than a "meta click."
* This is reliable on Windows and Linux, unlike: <p> <pre>
*     evt.isPopupTrigger()
* <p> </pre> which is obviously buggy. - 9/28/98 JH
*/
public static boolean isRightClick(MouseEvent evt) {
    return (evt.getModifiers() & MouseEvent.META_MASK) > 0;
}
//---------- Private Methods -----------------------------
//--- Std
private static void print(String text) {
    System.out.println("WindowLib" + text);
}

} // End class
