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");
}
}