/******************************************************************************* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Tiny Look and Feel * * (C) Copyright 2003 - 2007 Hans Bickel * * For * licensing information and credits, please refer to the * comment in file * de.muntjak.tinylookandfeel.TinyLookAndFeel * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ package de.muntjak.tinylookandfeel; import java.awt.Dimension; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.HashMap; import java.util.Vector; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JTable; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicTableHeaderUI; import javax.swing.table.JTableHeader; import javax.swing.table.TableModel; import de.muntjak.tinylookandfeel.controlpanel.TinyTableModel; import de.muntjak.tinylookandfeel.table.SortableTableData; import de.muntjak.tinylookandfeel.table.TinyTableHeaderRenderer; /** * TinyTableHeaderUI * * @version 1.0 * @author Hans Bickel */ @SuppressWarnings ( { "all" } ) public class TinyTableHeaderUI extends BasicTableHeaderUI { /** Client property key */ public static final String ROLLOVER_COLUMN_KEY = "rolloverColumn"; /** Client property key */ public static final String SORTED_COLUMN_KEY = "sortedColumn"; /** Client property key */ public static final String SORTING_DIRECTION_KEY = "sortingDirection"; // Action command constants private static final int ADD_COLUMN = 0; private static final int REMOVE_COLUMN = 1; private static final int REPLACE_COLUMN = 2; /** * The horizontal distance the mouse must move to be recognized as a mouse * drag. */ private static final int MINIMUM_DRAG_DISTANCE = 5; private static final HashMap sortingCache = new HashMap (); protected SortableTableHandler handler; protected TinyTableHeaderRenderer headerRenderer; public TinyTableHeaderUI () { super (); } public static ComponentUI createUI ( JComponent header ) { return new TinyTableHeaderUI (); } protected void installListeners () { super.installListeners (); // new in 1.3.6 // install our own renderer so we can realize rollovers // and arrow icons (sortable table models only) headerRenderer = new TinyTableHeaderRenderer (); header.setDefaultRenderer ( headerRenderer ); // new in 1.3.6 // support for sortable table models handler = new SortableTableHandler (); header.addMouseListener ( handler ); header.addMouseMotionListener ( handler ); header.getColumnModel ().addColumnModelListener ( handler ); SortingInformation sortingInfo = ( SortingInformation ) sortingCache .get ( header ); if ( sortingInfo != null ) { handler.restoreSortingInformation ( header, sortingInfo ); } } protected void uninstallListeners () { super.uninstallListeners (); // Remove sorting information - even if we only // switch TinyLaF themes, we cannot preserve state handler.removeSortingInformation (); header.removeMouseListener ( handler ); header.removeMouseMotionListener ( handler ); header.getColumnModel ().removeColumnModelListener ( handler ); // if(header.getTable() != null) { // TableModel model = header.getTable().getModel(); // // if(model != null) { // model.removeTableModelListener(handler); // } // } } public Dimension getPreferredSize ( JComponent c ) { // this is for the very special case that the header value // of the 1st column is an empty string and no custom header // renderers were defined. // In this case, the dimension returned by super.getPreferredSize() // has a height of 2 but that's not what we want... Dimension d = super.getPreferredSize ( c ); d.height = Math.max ( 16, d.height ); return d; } /** * Call this method to programmatically initiate sorting on (sortable) table * models. Especially if your data is sorted by default, you should call this * method before the table is displayed the first time. * * @param columns array of column indices sorted by priority (highest priority * first) * @param sortingDirections array containing the sorting direction for each * sorted column. Values are either * <ul> * <li><code>de.muntjak.tinylookandfeel.table.SortableTableData.SORT_ASCENDING</code> * or * <li><code>de.muntjak.tinylookandfeel.table.SortableTableData.SORT_DESCENDING</code> * </ul> * @param table the table displaying the data * @throws IllegalArgumentException If any of the arguments is * <code>null</code> or arguments <code>colums</code> and * <code>sortingDirections</code> are of different length */ public void sortColumns ( int [] columns, int [] sortingDirections, JTable table ) { if ( handler == null ) return; handler.sortColumns ( columns, sortingDirections, table ); } /** * Sets horizontal alignments of table header renderers where an index in the * argument array corresponds to a column index. <br> * Note: If the length of the argument array is less than the number of * columns, unspecified columns default to <code>CENTER</code> alignment. If * the length of the argument array is greater than the number of columns, * surplus information will be ignored. * * @param alignments array of the following constants defined in * <code>SwingConstants</code>: <code>LEFT</code>, * <code>CENTER</code>, <code>RIGHT</code>, * <code>LEADING</code> or <code>TRAILING</code>. */ public void setHorizontalAlignments ( int [] alignments ) { if ( headerRenderer == null ) return; headerRenderer.setHorizontalAlignments ( alignments ); } /** * A data container used to store sorting information between instantiations * (LAF change) * * @author Hans Bickel */ private class SortingInformation { private Vector sortedViewColumns; private Vector sortedModelColumns; private Vector sortingDirections; SortingInformation ( Vector sortedViewColumns, Vector sortedModelColumns, Vector sortingDirections ) { this.sortedViewColumns = sortedViewColumns; this.sortedModelColumns = sortedModelColumns; this.sortingDirections = sortingDirections; } } /** * This handler is for table headers whose table model implements * SortableTableData. It is attached to the header in createUI(JComponent) and * keeps track of the sorting direction and the sorted column. * * @author Hans Bickel * @since 1.3.6 */ private class SortableTableHandler implements MouseListener, MouseMotionListener, TableColumnModelListener { // -1 means that no column is currently a rollover candidate, // else it is the index of the rollover column private int rolloverColumn = -1; private int pressedColumn = -1; private Vector sortedViewColumns = new Vector (); private Vector sortedModelColumns = new Vector (); private Vector sortingDirections = new Vector (); private boolean mouseInside = false; private boolean mouseDragged = false; private boolean inDrag = false; private Point pressedPoint; void sortColumns ( int [] columns, int [] directions, JTable table ) { if ( columns == null ) { throw new IllegalArgumentException ( "columns argument may not be null" ); } if ( directions == null ) { throw new IllegalArgumentException ( "directions argument may not be null" ); } if ( columns.length != directions.length ) { throw new IllegalArgumentException ( "columns argument and directions argument must be of equal length" ); } if ( columns.length > table.getColumnCount () ) { throw new IllegalArgumentException ( "Length of columns argument is greater than number of table columns" ); } JTableHeader header = table.getTableHeader (); SortableTableData model = getTableModel ( header ); if ( model == null ) return; sortedViewColumns.clear (); sortedModelColumns.clear (); sortingDirections.clear (); for ( int i = 0 ; i < columns.length ; i++ ) { sortedViewColumns.add ( new Integer ( columns [ i ] ) ); sortedModelColumns.add ( new Integer ( getModelColumn ( header, columns [ i ] ) ) ); sortingDirections.add ( new Integer ( directions [ i ] ) ); } header.putClientProperty ( SORTED_COLUMN_KEY, vectorToIntArray ( sortedViewColumns ) ); header.putClientProperty ( SORTING_DIRECTION_KEY, vectorToIntArray ( sortingDirections ) ); model.sortColumns ( vectorToIntArray ( sortedModelColumns ), vectorToIntArray ( sortingDirections ), table ); header.repaint (); } // MouseListener implementation public void mouseEntered ( MouseEvent e ) { mouseInside = true; if ( mouseDragged ) return; SortableTableData model = getTableModel ( e.getSource () ); if ( model == null ) return; JTableHeader header = ( JTableHeader ) e.getSource (); int viewColumn = header.columnAtPoint ( e.getPoint () ); int modelColumn = getModelColumnAt ( e ); if ( !model.isColumnSortable ( modelColumn ) ) { if ( rolloverColumn != -1 ) { rolloverColumn = -1; header.putClientProperty ( ROLLOVER_COLUMN_KEY, null ); } } else { rolloverColumn = viewColumn; header.putClientProperty ( ROLLOVER_COLUMN_KEY, new Integer ( rolloverColumn ) ); } header.repaint (); } public void mouseExited ( MouseEvent e ) { mouseInside = false; JTableHeader header = ( JTableHeader ) e.getSource (); if ( inDrag && header.getReorderingAllowed () ) return; SortableTableData model = getTableModel ( e.getSource () ); if ( model == null ) return; if ( rolloverColumn != -1 ) { rolloverColumn = -1; header.putClientProperty ( ROLLOVER_COLUMN_KEY, null ); header.repaint (); } } public void mouseReleased ( MouseEvent e ) { inDrag = false; if ( e.isPopupTrigger () ) { // showHeaderPopup(e); return; } if ( !mouseInside ) { mouseDragged = false; return; } SortableTableData model = getTableModel ( e.getSource () ); if ( model == null ) { mouseDragged = false; return; } JTableHeader header = ( JTableHeader ) e.getSource (); int viewColumn = header.columnAtPoint ( e.getPoint () ); if ( viewColumn == -1 ) { mouseDragged = false; return; } int modelColumn = getModelColumnAt ( e ); if ( !model.isColumnSortable ( modelColumn ) ) { if ( rolloverColumn != -1 ) { rolloverColumn = -1; header.putClientProperty ( ROLLOVER_COLUMN_KEY, null ); } } else { rolloverColumn = viewColumn; header.putClientProperty ( ROLLOVER_COLUMN_KEY, new Integer ( rolloverColumn ) ); } if ( mouseDragged ) { mouseDragged = false; return; } if ( !model.isColumnSortable ( modelColumn ) ) return; if ( pressedColumn != viewColumn ) return; // for header renderer, sorted column must be viewColumn Integer vc = new Integer ( viewColumn ); if ( sortedViewColumns.contains ( vc ) ) { int index = sortedViewColumns.indexOf ( vc ); if ( e.isAltDown () ) { // remove clicked column from sorted columns sortedViewColumns.remove ( index ); sortedModelColumns.remove ( index ); sortingDirections.remove ( index ); } else if ( ( e.isControlDown () && model.supportsMultiColumnSort () ) || sortedModelColumns.size () == 1 ) { // change sorting direction of clicked column int sortingDirection = ( ( Integer ) sortingDirections.get ( index ) ) .intValue (); if ( sortingDirection != SortableTableData.SORT_DESCENDING ) { sortingDirection = SortableTableData.SORT_DESCENDING; } else { sortingDirection = SortableTableData.SORT_ASCENDING; } sortingDirections.remove ( index ); sortingDirections.add ( index, new Integer ( sortingDirection ) ); } else { // change sorting direction of clicked column int sortingDirection = ( ( Integer ) sortingDirections.get ( index ) ) .intValue (); if ( sortingDirection != SortableTableData.SORT_DESCENDING ) { sortingDirection = SortableTableData.SORT_DESCENDING; } else { sortingDirection = SortableTableData.SORT_ASCENDING; } // remove all sorted columns and initialize // sorted columns with clicked column sortedViewColumns.clear (); sortedModelColumns.clear (); sortingDirections.clear (); sortedViewColumns.add ( vc ); sortedModelColumns.add ( new Integer ( getModelColumn ( e, viewColumn ) ) ); sortingDirections.add ( new Integer ( sortingDirection ) ); } } else { if ( e.isAltDown () ) { // ignore return; } else if ( e.isControlDown () && model.supportsMultiColumnSort () ) { // add clicked column to sorted columns sortedViewColumns.add ( vc ); sortedModelColumns.add ( new Integer ( getModelColumn ( e, viewColumn ) ) ); sortingDirections.add ( new Integer ( SortableTableData.SORT_ASCENDING ) ); } else { // initialize sorted columns with clicked column sortedViewColumns.clear (); sortedModelColumns.clear (); sortingDirections.clear (); sortedViewColumns.add ( vc ); sortedModelColumns.add ( new Integer ( getModelColumn ( e, viewColumn ) ) ); sortingDirections.add ( new Integer ( SortableTableData.SORT_ASCENDING ) ); } } header.putClientProperty ( SORTED_COLUMN_KEY, vectorToIntArray ( sortedViewColumns ) ); header.putClientProperty ( SORTING_DIRECTION_KEY, vectorToIntArray ( sortingDirections ) ); model.sortColumns ( vectorToIntArray ( sortedModelColumns ), vectorToIntArray ( sortingDirections ), header.getTable () ); header.repaint (); } private int [] vectorToIntArray ( Vector v ) { int [] ret = new int [ v.size () ]; for ( int i = 0 ; i < ret.length ; i++ ) { ret [ i ] = ( ( Integer ) v.get ( i ) ).intValue (); } return ret; } public void mousePressed ( MouseEvent e ) { if ( e.isPopupTrigger () ) { // showHeaderPopup(e); return; } JTableHeader header = ( JTableHeader ) e.getSource (); pressedPoint = e.getPoint (); pressedColumn = header.columnAtPoint ( pressedPoint ); mouseDragged = false; } public void mouseClicked ( MouseEvent e ) { } // MouseMotionListener implementation public void mouseDragged ( MouseEvent e ) { SortableTableData model = getTableModel ( e.getSource () ); if ( model == null ) return; inDrag = true; JTableHeader header = ( JTableHeader ) e.getSource (); if ( header.getResizingColumn () != null ) { // It's a resize, not a column move if ( !mouseDragged ) mouseDragged = true; } if ( !header.getReorderingAllowed () ) { int modelColumn = getModelColumnAt ( e ); if ( !model.isColumnSortable ( modelColumn ) ) { header.putClientProperty ( ROLLOVER_COLUMN_KEY, null ); header.repaint (); return; } } if ( !mouseDragged && isMouseDragged ( e.getPoint (), pressedPoint ) ) { mouseDragged = true; } if ( !mouseInside ) { // ! don't set rolloverColumn to -1 header.putClientProperty ( ROLLOVER_COLUMN_KEY, null ); } else { if ( !header.getReorderingAllowed () ) { int viewColumn = header.columnAtPoint ( e.getPoint () ); if ( viewColumn != rolloverColumn ) { rolloverColumn = viewColumn; } } if ( rolloverColumn != -1 ) { header.putClientProperty ( ROLLOVER_COLUMN_KEY, new Integer ( rolloverColumn ) ); } } header.repaint (); } public void mouseMoved ( MouseEvent e ) { if ( !mouseInside ) return; JTableHeader header = ( JTableHeader ) e.getSource (); int viewColumn = header.columnAtPoint ( e.getPoint () ); if ( viewColumn == -1 ) return; SortableTableData model = getTableModel ( e.getSource () ); if ( model == null ) return; int modelColumn = getModelColumnAt ( e ); if ( !model.isColumnSortable ( modelColumn ) ) { if ( rolloverColumn != -1 ) { rolloverColumn = -1; header.putClientProperty ( ROLLOVER_COLUMN_KEY, null ); header.repaint (); } return; } // rolloverColumn must be viewColumn, not modelColumn if ( viewColumn != rolloverColumn ) { rolloverColumn = viewColumn; header.putClientProperty ( ROLLOVER_COLUMN_KEY, new Integer ( rolloverColumn ) ); header.repaint (); } } // TableColumnModelListener implementation public void columnAdded ( TableColumnModelEvent e ) { // System.out.println("columnAdded at " + e.getToIndex()); removeSorting (); } public void columnMoved ( TableColumnModelEvent e ) { if ( e.getFromIndex () == e.getToIndex () ) return; if ( header == null ) return; // update rolloverColumn if ( rolloverColumn == e.getFromIndex () ) { rolloverColumn = e.getToIndex (); if ( mouseInside ) { header.putClientProperty ( ROLLOVER_COLUMN_KEY, new Integer ( rolloverColumn ) ); } } // update sorted column(s) int [] sc = vectorToIntArray ( sortedViewColumns ); boolean changed = false; // Note: With multiple sorted columns it might easily // be that both the column at FromIndex and the column // at ToIndex are sortable for ( int i = 0 ; i < sc.length ; i++ ) { if ( sc [ i ] == e.getFromIndex () ) { sc [ i ] = e.getToIndex (); changed = true; } else if ( sc [ i ] == e.getToIndex () ) { sc [ i ] = e.getFromIndex (); changed = true; } } if ( changed ) { sortedViewColumns.clear (); for ( int i = 0 ; i < sc.length ; i++ ) { sortedViewColumns.add ( new Integer ( sc [ i ] ) ); } header.putClientProperty ( SORTED_COLUMN_KEY, vectorToIntArray ( sortedViewColumns ) ); } } public void columnRemoved ( TableColumnModelEvent e ) { // System.out.println("columnRemoved at " + e.getFromIndex()); removeSorting (); } public void columnMarginChanged ( ChangeEvent e ) { } public void columnSelectionChanged ( ListSelectionEvent e ) { } // Helper methods private void removeSorting () { if ( header == null ) return; // remove rolloverColumn if ( rolloverColumn != -1 ) { rolloverColumn = -1; header.putClientProperty ( ROLLOVER_COLUMN_KEY, new Integer ( rolloverColumn ) ); } sortedModelColumns.clear (); sortedViewColumns.clear (); sortingDirections.clear (); header.putClientProperty ( SORTING_DIRECTION_KEY, null ); header.putClientProperty ( SORTED_COLUMN_KEY, null ); header.repaint (); } void removeSortingInformation () { if ( header == null ) return; SortableTableData model = getTableModel ( header ); if ( model == null ) return; // cache sorting information sortingCache.put ( header, new SortingInformation ( sortedViewColumns, sortedModelColumns, sortingDirections ) ); // We don't have to call removeSorting() model.sortColumns ( new int [] {}, new int [] {}, header.getTable () ); header.repaint (); } void restoreSortingInformation ( JTableHeader header, SortingInformation sortingInfo ) { if ( header == null ) return; SortableTableData model = getTableModel ( header ); if ( model == null ) return; sortedViewColumns = sortingInfo.sortedViewColumns; sortedModelColumns = sortingInfo.sortedModelColumns; sortingDirections = sortingInfo.sortingDirections; model.sortColumns ( vectorToIntArray ( sortedModelColumns ), vectorToIntArray ( sortingDirections ), header.getTable () ); header.repaint (); } /** * @param source the table header * @return the TableModel to work on or <code>null</code> if either table * is <code>null</code>, table model is <code>null</code> or * table model doesn't implement <code>SortableTableData</code> */ private SortableTableData getTableModel ( Object source ) { return getTableModel ( ( JTableHeader ) source ); } private SortableTableData getTableModel ( JTableHeader header ) { JTable table = header.getTable (); if ( table == null ) return null; TableModel model = table.getModel (); if ( ! ( model instanceof SortableTableData ) ) return null; // if(!tableModelListenerInstalled) { // tableModelListenerInstalled = true; // model.addTableModelListener(handler); // } return ( SortableTableData ) model; } /** * @param e * @return the logical column index */ private int getModelColumnAt ( MouseEvent e ) { JTableHeader header = ( JTableHeader ) e.getSource (); int viewColumn = header.columnAtPoint ( e.getPoint () ); if ( viewColumn == -1 ) return -1; return header.getColumnModel ().getColumn ( viewColumn ).getModelIndex (); } /** * @param e * @param viewColumn * @return the model column index corresponding to the view column index */ private int getModelColumn ( MouseEvent e, int viewColumn ) { if ( viewColumn == -1 ) return -1; JTableHeader header = ( JTableHeader ) e.getSource (); return getModelColumn ( header, viewColumn ); } private int getModelColumn ( JTableHeader header, int viewColumn ) { return header.getColumnModel ().getColumn ( viewColumn ).getModelIndex (); } private boolean isMouseDragged ( Point p1, Point p2 ) { if ( Math.abs ( p1.x - p2.x ) >= MINIMUM_DRAG_DISTANCE ) return true; return false; } private void showHeaderPopup ( MouseEvent e ) { final SortableTableData model = getTableModel ( e.getSource () ); if ( model == null ) return; JTableHeader header = ( JTableHeader ) e.getSource (); final int viewColumn = header.columnAtPoint ( e.getPoint () ); JPopupMenu menu = new JPopupMenu (); JMenu sub = new JMenu ( "Add" ); JMenuItem item = new JMenuItem ( "Double Column" ); item.addActionListener ( new ActionListener () { public void actionPerformed ( ActionEvent e ) { ( ( TinyTableModel ) model ).addColumn ( Double.class, viewColumn ); } } ); sub.add ( item ); item = new JMenuItem ( "Icon Column" ); item.addActionListener ( new ActionListener () { public void actionPerformed ( ActionEvent e ) { ( ( TinyTableModel ) model ).addColumn ( Icon.class, viewColumn ); } } ); sub.add ( item ); item = new JMenuItem ( "Integer Column" ); item.addActionListener ( new ActionListener () { public void actionPerformed ( ActionEvent e ) { ( ( TinyTableModel ) model ).addColumn ( Integer.class, viewColumn ); } } ); sub.add ( item ); item = new JMenuItem ( "String Column" ); item.addActionListener ( new ActionListener () { public void actionPerformed ( ActionEvent e ) { ( ( TinyTableModel ) model ).addColumn ( String.class, viewColumn ); } } ); sub.add ( item ); menu.add ( sub ); menu.addSeparator (); item = new JMenuItem ( "Remove Column" ); item.addActionListener ( new ActionListener () { public void actionPerformed ( ActionEvent e ) { ( ( TinyTableModel ) model ).removeColumn ( viewColumn ); } } ); if ( ( ( TinyTableModel ) model ).getColumnCount () < 2 ) { item.setEnabled ( false ); } menu.add ( item ); menu.addSeparator (); item = new JMenuItem ( "Remove all Rows" ); item.addActionListener ( new ActionListener () { public void actionPerformed ( ActionEvent e ) { ( ( TinyTableModel ) model ).removeAllRows (); } } ); if ( ( ( TinyTableModel ) model ).getRowCount () == 0 ) { item.setEnabled ( false ); } menu.add ( item ); menu.addSeparator (); item = new JMenuItem ( "Create new Data" ); item.addActionListener ( new ActionListener () { public void actionPerformed ( ActionEvent e ) { ( ( TinyTableModel ) model ).createNewData (); } } ); if ( ( ( TinyTableModel ) model ).getRowCount () > 0 ) { item.setEnabled ( false ); } menu.add ( item ); menu.show ( header, e.getX (), 0 ); } } }