major optimization updates
[debian/openrocket] / src / net / sf / openrocket / util / GUIUtil.java
index 2f50a9cf92d11c75d2c197d3d02c6764a8b48663..831f2381afc44b53f13d84fb536cfc38d87572dd 100644 (file)
@@ -2,6 +2,7 @@ package net.sf.openrocket.util;
 
 import java.awt.Component;
 import java.awt.Container;
+import java.awt.Font;
 import java.awt.Image;
 import java.awt.KeyboardFocusManager;
 import java.awt.Point;
@@ -21,16 +22,16 @@ import java.io.IOException;
 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;
@@ -44,86 +45,104 @@ import javax.swing.JSpinner;
 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);
        }
        
        
@@ -139,53 +158,139 @@ public class GUIUtil {
                        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 disposed.  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 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
+                * other alternatives.
+                */
+               try {
+                       // Set system L&F
+                       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+                       
+                       // Check whether we have an ugly L&F
+                       LookAndFeel laf = UIManager.getLookAndFeel();
+                       if (laf == null ||
+                                       laf.getName().matches(".*[mM][oO][tT][iI][fF].*") ||
+                                       laf.getName().matches(".*[mM][eE][tT][aA][lL].*")) {
+                               
+                               // Search for better LAF
+                               UIManager.LookAndFeelInfo[] info = UIManager.getInstalledLookAndFeels();
+                               String lafNames[] = {
+                                               ".*[gG][tT][kK].*",
+                                               ".*[wW][iI][nN].*",
+                                               ".*[mM][aA][cC].*",
+                                               ".*[aA][qQ][uU][aA].*",
+                                               ".*[nN][iI][mM][bB].*"
+                               };
+                               
+                               lf: for (String lafName : lafNames) {
+                                       for (UIManager.LookAndFeelInfo l : info) {
+                                               if (l.getName().matches(lafName)) {
+                                                       UIManager.setLookAndFeel(l.getClassName());
+                                                       break lf;
+                                               }
+                                       }
+                               }
+                       }
+               } catch (Exception 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 
@@ -198,155 +303,164 @@ public class GUIUtil {
         * @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)
@@ -357,7 +471,7 @@ public class GUIUtil {
                        if (col < 0)
                                return;
                        col = table.convertColumnIndexToModel(col);
-                       if (col != clickColumn) 
+                       if (col != clickColumn)
                                return;
                        
                        int row = table.rowAtPoint(p);
@@ -371,18 +485,18 @@ public class GUIUtil {
                        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);
                        }
                }
-
-    }
-    
+               
+       }
+       
 }