/* * codjo.net * * Common Apache License 2.0 */ package net.codjo.utils.sql; import net.codjo.utils.TableMap; import java.awt.Component; import java.awt.Toolkit; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Vector; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.SwingConstants; import javax.swing.event.TableModelEvent; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; /** * Sert � trier les colonnes. * * @author $Author: marcona $ * @version $Revision: 1.6 $ * */ public class TableRendererSorter extends TableMap implements TableCellRenderer { int[] indexes; Vector sortingColumns = new Vector(); boolean ascending = true; int columnSorted = -1; int compares; JLabel Renderer; ImageIcon ascendingIcon; ImageIcon descendingIcon; JTable table; /** * Constructor for the TableRendererSorter object */ public TableRendererSorter() { indexes = new int[0]; initCustomHeaderRenderer(); } /** * Constructor for the TableRendererSorter object * * @param theTable Description of Parameter */ public TableRendererSorter(JTable theTable) { table = theTable; if (theTable instanceof GenericTable) { setModel(((GenericTable)theTable).getTableModel()); } else { setModel(theTable.getModel()); } initCustomHeaderRenderer(); } /** * Sets the Model attribute of the TableRendererSorter object * * @param model The new Model value */ public void setModel(TableModel model) { super.setModel(model); reallocateIndexes(); } /** * Sets the ValueAt attribute of the TableRendererSorter object * * @param aValue The new ValueAt value * @param aRow The new ValueAt value * @param aColumn The new ValueAt value */ public void setValueAt(Object aValue, int aRow, int aColumn) { checkModel(); model.setValueAt(aValue, indexes[aRow], aColumn); } /** * Converti l'index tri� en index du model origine. * * @param row l'index trie. * * @return l'index converti du model origine. */ public int getConvertedIndex(int row) { return indexes[row]; } /** * Converti l'index du model origine en index tri�. * * @param row l'index du model. * * @return l'index tri�. */ public int getRealIndex(int row) { int realIdx = -1; for (int i = 0; i < indexes.length; i++) { if (indexes[i] == row) { realIdx = i; } } return realIdx; } // The mapping only affects the contents of the data rows. // Pass all requests to these rows through the mapping array: "indexes". /** * Gets the ValueAt attribute of the TableRendererSorter object * * @param aRow Description of Parameter * @param aColumn Description of Parameter * * @return The ValueAt value */ public Object getValueAt(int aRow, int aColumn) { checkModel(); return model.getValueAt(indexes[aRow], aColumn); } /** * Gets the TableCellRendererComponent attribute of the TableRendererSorter object * * @param Table Description of Parameter * @param Value Description of Parameter * @param Selected Description of Parameter * @param HasFocus Description of Parameter * @param Row Description of Parameter * @param Col Description of Parameter * * @return The TableCellRendererComponent value */ public Component getTableCellRendererComponent(JTable Table, Object Value, boolean Selected, boolean HasFocus, int Row, int Col) { // Assign the column's name Renderer.setText(Value.toString()); if (Table.convertColumnIndexToModel(Col) == columnSorted) { //Le booleen � d�j� �t� invers� alors on inverse l'icone renvoy� / ascending Renderer.setIcon(ascending ? descendingIcon : ascendingIcon); } else { Renderer.setIcon(null); } return Renderer; } /** * Overview. * * <p> * Description * </p> * * @param row1 Description of Parameter * @param row2 Description of Parameter * @param column Description of Parameter * * @return Description of the Returned Value */ public int compareRowsByColumn(int row1, int row2, int column) { Class type = model.getColumnClass(column); TableModel data = model; Object o1; Object o2; int columnView = table.convertColumnIndexToView(column); TableCellRenderer tcr = table.getCellRenderer(row1, columnView); if ((tcr.getClass() == DefaultTableCellRenderer.class) || ("class javax.swing.JTable$NumberRenderer".equals(tcr.getClass().toString())) || ("class javax.swing.JTable$BooleanRenderer".equals(tcr.getClass().toString())) || ("class javax.swing.JTable$DoubleRenderer".equals(tcr.getClass().toString())) || ("class net.codjo.gui.renderer.NumberFormatRenderer".equals(tcr.getClass().toString()))) { o1 = data.getValueAt(row1, column); o2 = data.getValueAt(row2, column); } else { o1 = tcr.getTableCellRendererComponent(table, data.getValueAt(row1, column), false, false, row1, columnView); o1 = ((JLabel)o1).getText(); o2 = tcr.getTableCellRendererComponent(table, data.getValueAt(row2, column), false, false, row2, columnView); o2 = ((JLabel)o2).getText(); if ((type != java.util.Date.class) && (type.getSuperclass() != java.util.Date.class)) { type = String.class; } } // If both values are null return 0 if (o1 == null && o2 == null) { return 0; } else if (o1 == null) { // Define null less than everything. return -1; } else if (o2 == null) { return 1; } /* * We copy all returned values from the getValue call in case * an optimised model is reusing one object to return many values. * The Number subclasses in the JDK are immutable and so will not be used in * this way but other subclasses of Number might want to do this to save * space and avoid unnecessary heap allocation. */ if ((type.getSuperclass() == java.lang.Number.class) || (type == java.lang.Number.class)) { Number n1 = (Number)data.getValueAt(row1, column); double d1 = n1.doubleValue(); Number n2 = (Number)data.getValueAt(row2, column); double d2 = n2.doubleValue(); if (d1 < d2) { return -1; } else if (d1 > d2) { return 1; } else { return 0; } } else if ((type == java.util.Date.class) || (type.getSuperclass() == java.util.Date.class)) { java.util.Date d1 = (java.util.Date)data.getValueAt(row1, column); java.util.Date d2 = (java.util.Date)data.getValueAt(row2, column); if (d1 == null && d2 == null) { return 0; } else if (d1 == null) { return -1; } else if (d2 == null) { return 1; } long n1 = d1.getTime(); long n2 = d2.getTime(); if (n1 < n2) { return -1; } else if (n1 > n2) { return 1; } else { return 0; } } else if (type == String.class) { String s1; String s2; if (table == null) { s1 = (String)data.getValueAt(row1, column); s2 = (String)data.getValueAt(row2, column); } else { s1 = (String)o1; s2 = (String)o2; } int result = s1.compareTo(s2); if (result < 0) { return -1; } else if (result > 0) { return 1; } else { return 0; } } else if (type == Boolean.class) { Boolean bool1 = (Boolean)data.getValueAt(row1, column); boolean b1 = bool1.booleanValue(); Boolean bool2 = (Boolean)data.getValueAt(row2, column); boolean b2 = bool2.booleanValue(); if (b1 == b2) { return 0; } else if (b1) { // Define false < true return 1; } else { return -1; } } else { Object v1 = data.getValueAt(row1, column); String s1 = v1.toString(); Object v2 = data.getValueAt(row2, column); String s2 = v2.toString(); int result = s1.compareTo(s2); if (result < 0) { return -1; } else if (result > 0) { return 1; } else { return 0; } } } /** * DOCUMENT ME! * * @param row1 Description of Parameter * @param row2 Description of Parameter * * @return Description of the Returned Value */ public int compare(int row1, int row2) { compares++; for (int level = 0; level < sortingColumns.size(); level++) { Integer column = (Integer)sortingColumns.elementAt(level); int result = compareRowsByColumn(row1, row2, column.intValue()); if (result != 0) { return ascending ? result : -result; } } return 0; } /** * Overview. * * <p> * Description * </p> */ public void reallocateIndexes() { int rowCount = model.getRowCount(); columnSorted = -1; // Set up a new array of indexes with the right number of elements // for the new data model. indexes = new int[rowCount]; // Initialise with the identity mapping. for (int row = 0; row < rowCount; row++) { indexes[row] = row; } checkModel(); } /** * Overview. * * <p> * Description * </p> * * @param evt Description of Parameter */ public void tableChanged(TableModelEvent evt) { columnSorted = -1; table.getTableHeader().repaint(); if (model.getRowCount() != indexes.length) { reallocateIndexes(); } super.tableChanged(evt); } /** * Overview. * * <p> * Description * </p> */ public void checkModel() { if (indexes.length != model.getRowCount()) { System.err.println("Sorter not informed of a change in model. : " + indexes.length + " != " + model.getRowCount()); } } /** * Overview. * * <p> * Description * </p> * * @param sender Description of Parameter */ public void sort(Object sender) { checkModel(); compares = 0; // n2sort(); // qsort(0, indexes.length-1); shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length); } /** * Overview. * * <p> * Description * </p> */ public void n2sort() { for (int i = 0; i < getRowCount(); i++) { for (int j = i + 1; j < getRowCount(); j++) { if (compare(indexes[i], indexes[j]) == -1) { swap(i, j); } } } } // This is a home-grown implementation which we have not had time // to research - it may perform poorly in some circumstances. It // requires twice the space of an in-place algorithm and makes // NlogN assigments shuttling the values between the two // arrays. The number of compares appears to vary between N-1 and // NlogN depending on the initial order but the main reason for // using it here is that, unlike qsort, it is stable. /** * Overview. * * <p> * Description * </p> * * @param from Description of Parameter * @param to Description of Parameter * @param low Description of Parameter * @param high Description of Parameter */ public void shuttlesort(int[] from, int[] to, int low, int high) { if (high - low < 2) { return; } int middle = (low + high) / 2; shuttlesort(to, from, low, middle); shuttlesort(to, from, middle, high); int p = low; int q = middle; if (high - low >= 4 && compare(from[middle - 1], from[middle]) <= 0) { for (int i = low; i < high; i++) { to[i] = from[i]; } return; } // A normal merge. for (int i = low; i < high; i++) { if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) { to[i] = from[p++]; } else { to[i] = from[q++]; } } } /** * Overview. * * <p> * Description * </p> * * @param i Description of Parameter * @param j Description of Parameter */ public void swap(int i, int j) { int tmp = indexes[i]; indexes[i] = indexes[j]; indexes[j] = tmp; } /** * Overview. * * <p> * Description * </p> * * @param column Description of Parameter */ public void sortByColumn(int column) { sortByColumn(column, ascending); } /** * Overview. * * <p> * Description * </p> * * @param column Description of Parameter * @param ascending Description of Parameter */ public void sortByColumn(int column, boolean ascending) { columnSorted = column; sortingColumns.removeAllElements(); sortingColumns.addElement(new Integer(column)); sort(this); super.tableChanged(new TableModelEvent(this)); this.ascending = !ascending; } // There is no-where else to put this. // Add a mouse listener to the Table to trigger a table sort // when a column heading is clicked in the JTable. /** * Adds a feature to the MouseListenerToHeaderInTable attribute of the * TableRendererSorter object * * @param table The feature to be added to the MouseListenerToHeaderInTable attribute */ public void addMouseListenerToHeaderInTable(JTable table) { final TableRendererSorter sorter = this; final JTable tableView = table; tableView.setColumnSelectionAllowed(false); MouseAdapter listMouseListener = new MouseAdapter() { /** * Overview. * * <p> * Description * </p> * * @param e Description of Parameter */ public void mousePressed(MouseEvent e) { TableColumnModel columnModel = tableView.getColumnModel(); int viewColumn = columnModel.getColumnIndexAtX(e.getX()); int column = tableView.convertColumnIndexToModel(viewColumn); if (e.getClickCount() > 1 && column != -1) { sorter.sortByColumn(column); } } }; // Add the mouse listener JTableHeader th = tableView.getTableHeader(); th.addMouseListener(listMouseListener); } /** * Overview. * * <p> * Description * </p> * * @param table Description of Parameter */ public void changeHeaderRenderer(JTable table) { columnSorted = -1; TableColumn aCol; int nbColumn = getColumnCount(); for (int i = 0; i < nbColumn; i++) { aCol = table.getColumn(getColumnName(i)); aCol.setHeaderRenderer(this); } } /** * Overview. * * <p> * Description * </p> */ private final void initCustomHeaderRenderer() { // ----------------------------------------------------- // 1. Load the images that represent the widget java.net.URL url; // 1.1 Load AscendingIcon url = getClass().getResource("Ascending.gif"); if (url != null) { ascendingIcon = new ImageIcon(Toolkit.getDefaultToolkit().createImage(url)); } else { ascendingIcon = null; } // 1.2 Load DescendingIcon url = getClass().getResource("Descending.gif"); if (url != null) { descendingIcon = new ImageIcon(Toolkit.getDefaultToolkit().createImage(url)); } else { descendingIcon = null; } // ----------------------------------------------------- // 2. Set up the Renderer Renderer = new JLabel(); Renderer.setOpaque(true); Renderer.setHorizontalAlignment(SwingConstants.CENTER); Renderer.setHorizontalTextPosition(SwingConstants.RIGHT); Renderer.setVerticalTextPosition(SwingConstants.CENTER); Renderer.setBorder(new javax.swing.border.EtchedBorder( javax.swing.border.EtchedBorder.LOWERED)); } }