/*******************************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 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 );
}
}
}