package com.superliminal.uiutil; import javax.swing.*; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; import javax.swing.event.*; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.geom.Rectangle2D; import java.awt.datatransfer.*; import java.awt.event.*; import java.io.IOException; import java.net.InetAddress; /** * A collection of generally useful Swing utility methods. * * Copyright: Copyright (c) 2004 * Company: Superliminal Software * * @author Melinda Green */ public class StaticUtils { // to disallow instantiation private StaticUtils(){} /** * Adds a control hot key to the containing window of a component. * In the case of buttons and menu items it also attaches the given action to the component itself. * * @param key one of the KeyEvent keyboard constants * @param to component to map to * @param actionName unique action name for the component's action map * @param action callback to notify when control key is pressed */ public static void addHotKey(int key, JComponent to, String actionName, Action action) { KeyStroke keystroke = KeyStroke.getKeyStroke(key, java.awt.event.InputEvent.CTRL_MASK); InputMap map = to.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); map.put(keystroke, actionName); to.getActionMap().put(actionName, action); if(to instanceof JMenuItem) ((JMenuItem)to).setAccelerator(keystroke); if(to instanceof AbstractButton) // includes JMenuItem ((AbstractButton)to).addActionListener(action); } /** * Finds the top-level JFrame in the component tree containing a given component. * @param comp leaf component to search up from * @return the containing JFrame or null if none */ public static JFrame getTopFrame(Component comp) { if(comp == null) return null; while (comp.getParent() != null) comp = comp.getParent(); if (comp instanceof JFrame) return (JFrame) comp; return null; } public static Window getActiveWindow() { return KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); } public static void setWaitCursor() { if(getActiveWindow() != null) getActiveWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); } public static void setDefaultCursor() { if(getActiveWindow() != null) getActiveWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } /** * Different platforms use different mouse gestures as pop-up triggers. * This class unifies them. Just implement the abstract popUp method * to add your handler. */ public static abstract class PopperUpper extends MouseAdapter { // To work properly on all platforms, must check on mouse press as well as release public void mousePressed(MouseEvent e) { if(e.isPopupTrigger()) popUp(e); } public void mouseReleased(MouseEvent e) { if(e.isPopupTrigger()) popUp(e); } protected abstract void popUp(MouseEvent e); } // simple Clipboard string routines public static void placeInClipboard(String str) { Toolkit.getDefaultToolkit().getSystemClipboard().setContents( new StringSelection(str), null); } /** * @return String contained in system clipboard if any and if accessible to caller. */ public static String getFromClipboard() { try { return (String)Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null).getTransferData(DataFlavor.stringFlavor); } catch (UnsupportedFlavorException e) {} catch (IOException e) {} return null; } /** * Replaces the contents of a container with a centered label. * Useful for error messages or temporary messages like "Loading XXX View -- Please wait..." * which later get replaced with the real view when the server data arives, etc. */ public static void showMessageLabel(String text, Container in) { JLabel label = new JLabel("<html><h2>" + text + "</h2><html>"); label.setHorizontalAlignment(SwingConstants.CENTER); in.removeAll(); in.setLayout(new BorderLayout()); in.add(label); in.validate(); } /** * Draws the given string into the given graphics with the area behind the string * filled with a given background color. */ public static void fillString(String str, int x, int y, Color bg, Graphics g) { Rectangle2D strrect = g.getFontMetrics().getStringBounds(str, null); Color ocolor = g.getColor(); g.setColor(bg); g.fillRect((int)(x+strrect.getX()), (int)(y+strrect.getY()), (int)(strrect.getWidth()), (int)(strrect.getHeight())); g.setColor(ocolor); g.drawString(str, x, y); } /** * Sets the location of the given frame to be centered on the screen. * Precondition: Frame must already have its size set either directly or via pack(). * @param frame the frame to center. */ public static void center(JFrame frame) { Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); frame.setLocation( Math.max(0,screenSize.width/2 - frame.getWidth()/2), Math.max(0,screenSize.height/2 - frame.getHeight()/2)); } /** * Utility class that initializes a meduim sized, screen-centered, exit-on-close JFrame. * Mostly useful for simple example main programs. */ public static class QuickFrame extends JFrame { public QuickFrame(String title) { super(title); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(640, 480); center(this); } public QuickFrame(String title, Component content) { this(title); getContentPane().add(content); } } /** * Compares a screen rectangle to the current graphics screens. * @param rect represents the bounds of a window or other region in screen space. * @return true if the given rectangle is completely contained by one of the * current screens, false otherwise. */ public static boolean isOnScreen(Rectangle rect) { for (GraphicsDevice screenDevice : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) if (screenDevice.getDefaultConfiguration().getBounds().contains(rect)) return true; return false; } public static Color slightlyDifferentColor(Color from) { float rgb[] = new float[3]; from.getColorComponents(rgb); float offset = -.05f; if(rgb[0]<.5 && rgb[1]<.5 && rgb[2]<.5) offset *= -1; for(int i=0; i<3; i++) { rgb[i] += offset; rgb[i] = Math.min(rgb[i], 1); rgb[i] = Math.max(rgb[i], 0); } return new Color(rgb[0],rgb[1],rgb[2]); } /** * Description: A JTable with alternating row background colors. * half the rows are the component's natural background color, and the others are either darkened or lightened * versions of that color depending on the maximum color component. */ public class ZebraTable extends JTable { public ZebraTable() { super(); } public ZebraTable(TableModel model) { super(model); } private final Color altered = StaticUtils.slightlyDifferentColor(getBackground()); public Component prepareRenderer(TableCellRenderer renderer, int row, int col) { Component c = super.prepareRenderer(renderer, row, col); Color bg = getBackground(); c.setBackground(row % 2 == 0 && !isCellSelected(row, col) ? altered : bg); if(isCellSelected(row, col)) c.setBackground(getSelectionBackground()); return c; } } /** * Used to persist tab selection changes as user preferences. * This must only be called <i>after</i> all the tabs have been added * otherwise internal calls to setSelectedTab will cause the user preferences to be overwritten. * @param tabs the component to initialize and track. * @param key name of the property to set/get. */ public static void manageTabSelections(final JTabbedPane tabs, final String key) { // First, set initially selected tab if any. String lastSelected = PropertyManager.top.getProperty(key); if(lastSelected != null) { int i = tabs.indexOfTab(lastSelected); if(i >= 0) tabs.setSelectedIndex(i); } // Now, add change listener that tracks user selections. // This must be done *after* making the initial selection so as not to pick up that event. tabs.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if( ! tabs.isShowing()) return; String ss = tabs.getTitleAt(tabs.getSelectedIndex()); PropertyManager.userprefs.setProperty(key, ss); } }); } /** * Selection utility in the style of the JOptionPane.showXxxDialog methods. * Given a JTree, presents an option dialog presenting the tree allowing users to select a node. * @param tree is the tree to display * @param parent is the component to anchor the diaglog to * @return the path of the selected tree node or null if cancelled. */ public static TreePath showTreeNodeChooser(JTree tree, String title, Component parent) { final String OK = "OK", CANCEL = "Cancel"; final JButton ok_butt = new JButton(OK), cancel_butt = new JButton(CANCEL); final TreePath selected[] = new TreePath[] { tree.getLeadSelectionPath() }; // only an array so it can be final, yet mutable ok_butt.setEnabled(selected[0] != null); final JOptionPane option_pane = new JOptionPane(new JScrollPane(tree), JOptionPane.QUESTION_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{ok_butt, cancel_butt}); ok_butt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { option_pane.setValue(OK); } }); cancel_butt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { option_pane.setValue(CANCEL); selected[0] = null; } }); TreeSelectionListener tsl = new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { selected[0] = e.getNewLeadSelectionPath(); ok_butt.setEnabled(selected[0] != null); } }; JDialog dialog = option_pane.createDialog(parent, title); tree.addTreeSelectionListener(tsl); // to start monitoring user tree selections dialog.setVisible(true); // present modal tree dialog to user tree.removeTreeSelectionListener(tsl); // don't want to clutter caller's tree with listeners return OK.equals(option_pane.getValue()) ? selected[0] : null; } /** * Converts an IP address into a numeric value suitable for sorting via comparators. */ public static long ip2long(InetAddress ip) { byte[] addr = ip.getAddress(); long val = 0; for(int i=0; i<addr.length; i++) { long byteval = (0x000000FF & ((int)addr[addr.length-i-1])); val |= ((byteval << 8*i) & ~0L << 8*i); } return val; } /** * Presents a warning message to the user in a modal dialog along with a standard "don't show this again" checkbox. * Subsequent calls will simply do nothing any time after the user checks the check box and closes the dialog. * @param parent is a parent component to attach to. * @param skipKey is a string key used to find in the PropertyManager whether to skip showing this message. * Also used as the user preference key to set when/if the user ever checks the check box. * @param notice is the warning message to present. */ public static void conditionalWarning(final String notice, final String skipKey, Component parent) { class NotifyPanel extends JPanel { public NotifyPanel() { final JCheckBox enough = new JCheckBox("Don't show this message again", PropertyManager.getBoolean(skipKey, false)); enough.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { PropertyManager.userprefs.setProperty(skipKey, ""+enough.isSelected()); // save pref change. } }); setLayout(new BorderLayout()); add(new JLabel("<html><font size=+1>" + notice + "</font></html>"), BorderLayout.CENTER); add(enough, BorderLayout.SOUTH); } } if( ! PropertyManager.getBoolean(skipKey, false)) JOptionPane.showMessageDialog(parent, new NotifyPanel(), "Warning", JOptionPane.WARNING_MESSAGE); } /** * Same as 3-argument version but uses the active window as the parent. */ public static void conditionalWarning(final String notice, final String skipKey) { conditionalWarning(notice, skipKey, getActiveWindow()); } /** * A mouse listener that when added to JTables, JTrees, or JLists, * causes right-clicks on rows or nodes to select them just like a left click would. * For more information on this issue see: http://forums.java.net/jive/thread.jspa?messageID=107674 */ public static class RightClickSelector extends MouseAdapter { public void mousePressed(MouseEvent e) { if(e.getButton() != MouseEvent.BUTTON3) return; Object src = e.getSource(); if(src instanceof JTree) { JTree tree = (JTree)src; int clickedRow = tree.getRowForLocation(e.getX(), e.getY()); if( ! tree.isRowSelected(clickedRow)) tree.setSelectionRow(clickedRow); } else if(src instanceof JTable) { JTable table = (JTable)src; int clickedRow = table.rowAtPoint(e.getPoint()); if ( ! table.isRowSelected(clickedRow)) table.getSelectionModel().setSelectionInterval(clickedRow, clickedRow); } else if(src instanceof JList) { JList list = (JList)src; int clickedRow = list.locationToIndex(e.getPoint()); if( ! list.isSelectedIndex(clickedRow)) list.setSelectedIndex(clickedRow); } } } public static void main(String[] args) { conditionalWarning("You haven't yet chosen to ignore this warning.", "showtestwarning"); } }