import java.awt.Component;
import java.awt.Container;
+import java.awt.Font;
import java.awt.Image;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.Vector;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
+import javax.swing.BoundedRangeModel;
+import javax.swing.ComboBoxModel;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel;
import javax.swing.RootPaneContainer;
+import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
+import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
+import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.DefaultTreeSelectionModel;
-import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreeSelectionModel;
import net.sf.openrocket.gui.Resettable;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
public class GUIUtil {
-
+ private static final LogHelper log = Application.getLogger();
+
private static final KeyStroke ESCAPE = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
- private static final String CLOSE_ACTION_KEY = "escape:WINDOW_CLOSING";
+ private static final String CLOSE_ACTION_KEY = "escape:WINDOW_CLOSING";
+
+ private static final List<Image> images = new ArrayList<Image>();
+ static {
+ loadImage("pix/icon/icon-256.png");
+ loadImage("pix/icon/icon-064.png");
+ loadImage("pix/icon/icon-048.png");
+ loadImage("pix/icon/icon-032.png");
+ loadImage("pix/icon/icon-016.png");
+ }
+
+ private static void loadImage(String file) {
+ InputStream is;
+
+ is = ClassLoader.getSystemResourceAsStream(file);
+ if (is == null)
+ return;
+
+ try {
+ Image image = ImageIO.read(is);
+ images.add(image);
+ } catch (IOException ignore) {
+ ignore.printStackTrace();
+ }
+ }
+
- private static final List<Image> images = new ArrayList<Image>();
- static {
- loadImage("pix/icon/icon-256.png");
- loadImage("pix/icon/icon-064.png");
- loadImage("pix/icon/icon-048.png");
- loadImage("pix/icon/icon-032.png");
- loadImage("pix/icon/icon-016.png");
- }
- private static void loadImage(String file) {
- InputStream is;
-
- is = ClassLoader.getSystemResourceAsStream(file);
- if (is == null)
- return;
-
- try {
- Image image = ImageIO.read(is);
- images.add(image);
- } catch (IOException ignore) {
- ignore.printStackTrace();
- }
- }
-
-
- /**
- * Set suitable options for a single-use disposable dialog. This includes
- * setting ESC to close the dialog and adding the appropriate window icons.
- * If defaultButton is provided, it is set to the default button action.
- * <p>
- * The default button must be already attached to the dialog.
- *
- * @param dialog the dialog.
- * @param defaultButton the default button of the dialog, or <code>null</code>.
- */
- public static void setDisposableDialogOptions(JDialog dialog, JButton defaultButton) {
- installEscapeCloseOperation(dialog);
- setWindowIcons(dialog);
- addModelNullingListener(dialog);
- if (defaultButton != null) {
- setDefaultButton(defaultButton);
- }
- }
-
+ /**
+ * Set suitable options for a single-use disposable dialog. This includes
+ * setting ESC to close the dialog, adding the appropriate window icons and
+ * setting the location based on the platform. If defaultButton is provided,
+ * it is set to the default button action.
+ * <p>
+ * The default button must be already attached to the dialog.
+ *
+ * @param dialog the dialog.
+ * @param defaultButton the default button of the dialog, or <code>null</code>.
+ */
+ public static void setDisposableDialogOptions(JDialog dialog, JButton defaultButton) {
+ installEscapeCloseOperation(dialog);
+ setWindowIcons(dialog);
+ addModelNullingListener(dialog);
+ dialog.setLocationByPlatform(true);
+ dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ dialog.pack();
+ if (defaultButton != null) {
+ setDefaultButton(defaultButton);
+ }
+ }
+
/**
* Add the correct action to close a JDialog when the ESC key is pressed.
* The dialog is closed by sending is a WINDOW_CLOSING event.
*
* @param dialog the dialog for which to install the action.
*/
- public static void installEscapeCloseOperation(final JDialog dialog) {
- Action dispatchClosing = new AbstractAction() {
- public void actionPerformed(ActionEvent event) {
- dialog.dispatchEvent(new WindowEvent(dialog, WindowEvent.WINDOW_CLOSING));
- }
- };
- JRootPane root = dialog.getRootPane();
- root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ESCAPE, CLOSE_ACTION_KEY);
- root.getActionMap().put(CLOSE_ACTION_KEY, dispatchClosing);
+ public static void installEscapeCloseOperation(final JDialog dialog) {
+ Action dispatchClosing = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ log.user("Closing dialog " + dialog);
+ dialog.dispatchEvent(new WindowEvent(dialog, WindowEvent.WINDOW_CLOSING));
+ }
+ };
+ JRootPane root = dialog.getRootPane();
+ root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ESCAPE, CLOSE_ACTION_KEY);
+ root.getActionMap().put(CLOSE_ACTION_KEY, dispatchClosing);
}
throw new IllegalArgumentException("Attach button to a window first.");
}
if (!(w instanceof RootPaneContainer)) {
- throw new IllegalArgumentException("Button not attached to RootPaneContainer, w="+w);
+ throw new IllegalArgumentException("Button not attached to RootPaneContainer, w=" + w);
}
- ((RootPaneContainer)w).getRootPane().setDefaultButton(button);
+ ((RootPaneContainer) w).getRootPane().setDefaultButton(button);
}
-
+
/**
* Change the behavior of a component so that TAB and Shift-TAB cycles the focus of
* the components. This is necessary for e.g. <code>JTextArea</code>.
*
* @param c the component to modify
*/
- public static void setTabToFocusing(Component c) {
- Set<KeyStroke> strokes = new HashSet<KeyStroke>(Arrays.asList(KeyStroke.getKeyStroke("pressed TAB")));
- c.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, strokes);
- strokes = new HashSet<KeyStroke>(Arrays.asList(KeyStroke.getKeyStroke("shift pressed TAB")));
- c.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, strokes);
- }
+ public static void setTabToFocusing(Component c) {
+ Set<KeyStroke> strokes = new HashSet<KeyStroke>(Arrays.asList(KeyStroke.getKeyStroke("pressed TAB")));
+ c.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, strokes);
+ strokes = new HashSet<KeyStroke>(Arrays.asList(KeyStroke.getKeyStroke("shift pressed TAB")));
+ c.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, strokes);
+ }
+
+
-
-
- /**
- * Set the OpenRocket icons to the window icons.
- *
- * @param window the window to set.
- */
- public static void setWindowIcons(Window window) {
- window.setIconImages(images);
- }
-
- /**
- * Add a listener to the provided window that will call {@link #setNullModels(Component)}
- * on the window once it is closed. This method may only be used on single-use
- * windows and dialogs, that will never be shown again once closed!
- *
- * @param window the window to add the listener to.
- */
- public static void addModelNullingListener(final Window window) {
- window.addWindowListener(new WindowAdapter() {
+ /**
+ * Set the OpenRocket icons to the window icons.
+ *
+ * @param window the window to set.
+ */
+ public static void setWindowIcons(Window window) {
+ window.setIconImages(images);
+ }
+
+ /**
+ * Add a listener to the provided window that will call {@link #setNullModels(Component)}
+ * on the window once it is closed. This method may only be used on single-use
+ * windows and dialogs, that will never be shown again once closed!
+ *
+ * @param window the window to add the listener to.
+ */
+ public static void addModelNullingListener(final Window window) {
+ window.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
+ log.debug("Clearing all models of window " + window);
setNullModels(window);
+ MemoryManagement.collectable(window);
}
- });
- }
-
+ });
+ }
+
+
-
- /**
- * Set the best available look-and-feel into use.
- */
- public static void setBestLAF() {
+ /**
+ * Set the best available look-and-feel into use.
+ */
+ public static void setBestLAF() {
/*
* Set the look-and-feel. On Linux, Motif/Metal is sometimes incorrectly used
* which is butt-ugly, so if the system l&f is Motif/Metal, we search for a few
".*[nN][iI][mM][bB].*"
};
- lf: for (String lafName: lafNames) {
- for (UIManager.LookAndFeelInfo l: info) {
+ lf: for (String lafName : lafNames) {
+ for (UIManager.LookAndFeelInfo l : info) {
if (l.getName().matches(lafName)) {
UIManager.setLookAndFeel(l.getClassName());
break lf;
}
- }
+ }
}
}
} catch (Exception e) {
- System.err.println("Error setting LAF: " + e);
+ log.warn("Error setting LAF: " + e);
}
- }
-
-
+ }
+
+
+ /**
+ * Changes the size of the font of the specified component by the given amount.
+ *
+ * @param component the component for which to change the font
+ * @param size the change in the font size
+ */
+ public static void changeFontSize(JComponent component, float size) {
+ Font font = component.getFont();
+ font = font.deriveFont(font.getSize2D() + size);
+ component.setFont(font);
+ }
+
+
+ /**
+ * Changes the style of the font of the specified border.
+ *
+ * @param border the component for which to change the font
+ * @param style the change in the font style
+ */
+ public static void changeFontStyle(TitledBorder border, int style) {
+ /*
+ * There's been an NPE caused by the font changing, this is debug for it.
+ */
+ if (border == null) {
+ throw new BugException("border is null");
+ }
+ Font font = border.getTitleFont();
+ if (font == null) {
+ throw new BugException("Border font is null");
+ }
+ font = font.deriveFont(style);
+ if (font == null) {
+ throw new BugException("Derived font is null");
+ }
+ border.setTitleFont(font);
+ }
+
+
+
/**
* Traverses recursively the component tree, and sets all applicable component
* models to null, so as to remove the listener connections. After calling this
* @param c the component (<code>null</code> is ok)
*/
public static void setNullModels(Component c) {
- if (c==null)
+ if (c == null)
return;
// Remove various listeners
- for (ComponentListener l: c.getComponentListeners()) {
+ for (ComponentListener l : c.getComponentListeners()) {
c.removeComponentListener(l);
}
- for (FocusListener l: c.getFocusListeners()) {
+ for (FocusListener l : c.getFocusListeners()) {
c.removeFocusListener(l);
}
- for (MouseListener l: c.getMouseListeners()) {
+ for (MouseListener l : c.getMouseListeners()) {
c.removeMouseListener(l);
}
- for (PropertyChangeListener l: c.getPropertyChangeListeners()) {
+ for (PropertyChangeListener l : c.getPropertyChangeListeners()) {
c.removePropertyChangeListener(l);
}
- for (PropertyChangeListener l: c.getPropertyChangeListeners("model")) {
+ for (PropertyChangeListener l : c.getPropertyChangeListeners("model")) {
c.removePropertyChangeListener("model", l);
}
- for (PropertyChangeListener l: c.getPropertyChangeListeners("action")) {
+ for (PropertyChangeListener l : c.getPropertyChangeListeners("action")) {
c.removePropertyChangeListener("action", l);
}
// Remove models for known components
// Why the FSCK must this be so hard?!?!?
-
+
if (c instanceof JSpinner) {
- JSpinner spinner = (JSpinner)c;
- for (ChangeListener l: spinner.getChangeListeners()) {
+ JSpinner spinner = (JSpinner) c;
+ for (ChangeListener l : spinner.getChangeListeners()) {
spinner.removeChangeListener(l);
}
+ SpinnerModel model = spinner.getModel();
spinner.setModel(new SpinnerNumberModel());
+ if (model instanceof Invalidatable) {
+ ((Invalidatable) model).invalidate();
+ }
} else if (c instanceof JSlider) {
- JSlider slider = (JSlider)c;
- for (ChangeListener l: slider.getChangeListeners()) {
+ JSlider slider = (JSlider) c;
+ for (ChangeListener l : slider.getChangeListeners()) {
slider.removeChangeListener(l);
}
+ BoundedRangeModel model = slider.getModel();
slider.setModel(new DefaultBoundedRangeModel());
+ if (model instanceof Invalidatable) {
+ ((Invalidatable) model).invalidate();
+ }
} else if (c instanceof JComboBox) {
- JComboBox combo = (JComboBox)c;
- for (ActionListener l: combo.getActionListeners()) {
+ JComboBox combo = (JComboBox) c;
+ for (ActionListener l : combo.getActionListeners()) {
combo.removeActionListener(l);
}
+ ComboBoxModel model = combo.getModel();
combo.setModel(new DefaultComboBoxModel());
+ if (model instanceof Invalidatable) {
+ ((Invalidatable) model).invalidate();
+ }
} else if (c instanceof AbstractButton) {
- AbstractButton button = (AbstractButton)c;
- for (ActionListener l: button.getActionListeners()) {
+ AbstractButton button = (AbstractButton) c;
+ for (ActionListener l : button.getActionListeners()) {
button.removeActionListener(l);
}
+ Action model = button.getAction();
button.setAction(new AbstractAction() {
@Override
- public void actionPerformed(ActionEvent e) { }
+ public void actionPerformed(ActionEvent e) {
+ }
});
+ if (model instanceof Invalidatable) {
+ ((Invalidatable) model).invalidate();
+ }
} else if (c instanceof JTable) {
- JTable table = (JTable)c;
+ JTable table = (JTable) c;
+ TableModel model1 = table.getModel();
table.setModel(new DefaultTableModel());
+ if (model1 instanceof Invalidatable) {
+ ((Invalidatable) model1).invalidate();
+ }
+
+ TableColumnModel model2 = table.getColumnModel();
table.setColumnModel(new DefaultTableColumnModel());
+ if (model2 instanceof Invalidatable) {
+ ((Invalidatable) model2).invalidate();
+ }
+
+ ListSelectionModel model3 = table.getSelectionModel();
table.setSelectionModel(new DefaultListSelectionModel());
+ if (model3 instanceof Invalidatable) {
+ ((Invalidatable) model3).invalidate();
+ }
} else if (c instanceof JTree) {
- JTree tree = (JTree)c;
- tree.setModel(new DefaultTreeModel(new TreeNode() {
- @SuppressWarnings("unchecked")
- @Override
- public Enumeration children() {
- return new Vector().elements();
- }
- @Override
- public boolean getAllowsChildren() {
- return false;
- }
- @Override
- public TreeNode getChildAt(int childIndex) {
- return null;
- }
- @Override
- public int getChildCount() {
- return 0;
- }
- @Override
- public int getIndex(TreeNode node) {
- return 0;
- }
- @Override
- public TreeNode getParent() {
- return null;
- }
- @Override
- public boolean isLeaf() {
- return true;
- }
- }));
+ JTree tree = (JTree) c;
+ TreeModel model1 = tree.getModel();
+ tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode()));
+ if (model1 instanceof Invalidatable) {
+ ((Invalidatable) model1).invalidate();
+ }
+
+ TreeSelectionModel model2 = tree.getSelectionModel();
tree.setSelectionModel(new DefaultTreeSelectionModel());
+ if (model2 instanceof Invalidatable) {
+ ((Invalidatable) model2).invalidate();
+ }
} else if (c instanceof Resettable) {
- ((Resettable)c).resetModel();
+ ((Resettable) c).resetModel();
}
-
+
// Recurse the component
if (c instanceof Container) {
- Component[] cs = ((Container)c).getComponents();
- for (Component sub: cs)
+ Component[] cs = ((Container) c).getComponents();
+ for (Component sub : cs)
setNullModels(sub);
}
-
+
}
-
- /**
- * A mouse listener that toggles the state of a boolean value in a table model
- * when clicked on another column of the table.
- * <p>
- * NOTE: If the table model does not extend AbstractTableModel, the model must
- * fire a change event (which in normal table usage is not necessary).
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
- public static class BooleanTableClickListener extends MouseAdapter {
-
- private final JTable table;
- private final int clickColumn;
- private final int booleanColumn;
-
-
- public BooleanTableClickListener(JTable table) {
- this(table, 1, 0);
- }
-
-
- public BooleanTableClickListener(JTable table, int clickColumn, int booleanColumn) {
- this.table = table;
- this.clickColumn = clickColumn;
- this.booleanColumn = booleanColumn;
- }
-
+ /**
+ * A mouse listener that toggles the state of a boolean value in a table model
+ * when clicked on another column of the table.
+ * <p>
+ * NOTE: If the table model does not extend AbstractTableModel, the model must
+ * fire a change event (which in normal table usage is not necessary).
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+ public static class BooleanTableClickListener extends MouseAdapter {
+
+ private final JTable table;
+ private final int clickColumn;
+ private final int booleanColumn;
+
+
+ public BooleanTableClickListener(JTable table) {
+ this(table, 1, 0);
+ }
+
+
+ public BooleanTableClickListener(JTable table, int clickColumn, int booleanColumn) {
+ this.table = table;
+ this.clickColumn = clickColumn;
+ this.booleanColumn = booleanColumn;
+ }
+
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1)
if (col < 0)
return;
col = table.convertColumnIndexToModel(col);
- if (col != clickColumn)
+ if (col != clickColumn)
return;
int row = table.rowAtPoint(p);
Object value = model.getValueAt(row, booleanColumn);
if (!(value instanceof Boolean)) {
- throw new IllegalStateException("Table value at row="+row+" col="+
- booleanColumn + " is not a Boolean, value=" +value);
+ throw new IllegalStateException("Table value at row=" + row + " col=" +
+ booleanColumn + " is not a Boolean, value=" + value);
}
- Boolean b = (Boolean)value;
+ Boolean b = (Boolean) value;
b = !b;
model.setValueAt(b, row, booleanColumn);
if (model instanceof AbstractTableModel) {
- ((AbstractTableModel)model).fireTableCellUpdated(row, booleanColumn);
+ ((AbstractTableModel) model).fireTableCellUpdated(row, booleanColumn);
}
}
-
- }
-
+
+ }
+
}