View Javadoc

1   /***
2    * Copyright (c) 2003 held jointly by the individual authors.            
3    *                                                                          
4    * This library is free software; you can redistribute it and/or modify it    
5    * under the terms of the GNU Lesser General Public License as published      
6    * by the Free Software Foundation; either version 2.1 of the License, or 
7    * (at your option) any later version.                                            
8    *                                                                            
9    * This library is distributed in the hope that it will be useful, but 
10   * WITHOUT ANY WARRANTY; with out even the implied warranty of 
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
12   * GNU Lesser General Public License for more details.                                                  
13   *                                                                           
14   * You should have received a copy of the GNU Lesser General Public License   
15   * along with this library;  if not, write to the Free Software Foundation,   
16   * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.              
17   *                                                                            
18   * > http://www.gnu.org/copyleft/lesser.html                                  
19   * > http://www.opensource.org/licenses/lgpl-license.php
20   */
21  package net.mlw.vlh.swing.support;
22  
23  import java.awt.Color;
24  import java.awt.Component;
25  import java.awt.Graphics;
26  import java.awt.event.ActionEvent;
27  import java.awt.event.ActionListener;
28  import java.awt.event.MouseAdapter;
29  import java.awt.event.MouseEvent;
30  import java.awt.event.MouseListener;
31  import java.util.ArrayList;
32  import java.util.List;
33  
34  import javax.swing.Icon;
35  import javax.swing.JLabel;
36  import javax.swing.JTable;
37  import javax.swing.event.TableModelEvent;
38  import javax.swing.event.TableModelListener;
39  import javax.swing.table.AbstractTableModel;
40  import javax.swing.table.JTableHeader;
41  import javax.swing.table.TableCellRenderer;
42  import javax.swing.table.TableColumnModel;
43  import javax.swing.table.TableModel;
44  
45  /***
46   * @author Matthew L. Wilson
47   * @version $Revision: 1.1 $ $Date: 2005/03/16 12:16:23 $
48   */
49  public class DelegateToServiceTableSorter extends AbstractTableModel
50  {
51     protected TableModel tableModel;
52  
53     public static final int DESCENDING = -1;
54  
55     public static final int NOT_SORTED = 0;
56  
57     public static final int ASCENDING = 1;
58  
59     private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
60  
61     private JTableHeader tableHeader;
62  
63     private MouseListener mouseListener;
64  
65     private TableModelListener tableModelListener;
66  
67     private List sortingColumns = new ArrayList();
68  
69     public DelegateToServiceTableSorter()
70     {
71        this.mouseListener = new MouseHandler();
72        this.tableModelListener = new TableModelHandler();
73     }
74  
75     public DelegateToServiceTableSorter(TableModel tableModel)
76     {
77        this();
78        setTableModel(tableModel);
79     }
80  
81     public DelegateToServiceTableSorter(TableModel tableModel, JTableHeader tableHeader)
82     {
83        this();
84        setTableHeader(tableHeader);
85        setTableModel(tableModel);
86     }
87  
88     private void clearSortingState()
89     {
90     }
91  
92     public TableModel getTableModel()
93     {
94        return tableModel;
95     }
96  
97     public void setTableModel(TableModel tableModel)
98     {
99        if (this.tableModel != null)
100       {
101          this.tableModel.removeTableModelListener(tableModelListener);
102       }
103 
104       this.tableModel = tableModel;
105       if (this.tableModel != null)
106       {
107          this.tableModel.addTableModelListener(tableModelListener);
108       }
109 
110       clearSortingState();
111       fireTableStructureChanged();
112    }
113 
114    public JTableHeader getTableHeader()
115    {
116       return tableHeader;
117    }
118 
119    public void setTableHeader(JTableHeader tableHeader)
120    {
121       if (this.tableHeader != null)
122       {
123          this.tableHeader.removeMouseListener(mouseListener);
124          TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
125          if (defaultRenderer instanceof SortableHeaderRenderer)
126          {
127             this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
128          }
129       }
130       this.tableHeader = tableHeader;
131       if (this.tableHeader != null)
132       {
133          this.tableHeader.addMouseListener(mouseListener);
134          this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
135       }
136    }
137 
138    public boolean isSorting()
139    {
140       return sortingColumns.size() != 0;
141    }
142 
143    private Directive getDirective(int column)
144    {
145       for (int i = 0; i < sortingColumns.size(); i++)
146       {
147          Directive directive = (Directive) sortingColumns.get(i);
148          if (directive.column == column)
149          {
150             return directive;
151          }
152       }
153       return EMPTY_DIRECTIVE;
154    }
155 
156    public int getSortingStatus(int column)
157    {
158       return getDirective(column).direction;
159    }
160 
161    private void sortingStatusChanged()
162    {
163       clearSortingState();
164       fireTableDataChanged();
165       if (tableHeader != null)
166       {
167          tableHeader.repaint();
168       }
169    }
170 
171    public void setSortingStatus(int column, int status)
172    {
173       Directive directive = getDirective(column);
174       if (directive != EMPTY_DIRECTIVE)
175       {
176          sortingColumns.remove(directive);
177       }
178       if (status != NOT_SORTED)
179       {
180          sortingColumns.add(new Directive(column, status));
181       }
182       sortingStatusChanged();
183    }
184 
185    protected Icon getHeaderRendererIcon(int column, int size)
186    {
187       Directive directive = getDirective(column);
188       if (directive == EMPTY_DIRECTIVE)
189       {
190          return null;
191       }
192       return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));
193    }
194 
195    private void cancelSorting()
196    {
197       sortingColumns.clear();
198       sortingStatusChanged();
199    }
200 
201    // TableModel interface methods
202 
203    public int getRowCount()
204    {
205       return (tableModel == null) ? 0 : tableModel.getRowCount();
206    }
207 
208    public int getColumnCount()
209    {
210       return (tableModel == null) ? 0 : tableModel.getColumnCount();
211    }
212 
213    public String getColumnName(int column)
214    {
215       return tableModel.getColumnName(column);
216    }
217 
218    public Class getColumnClass(int column)
219    {
220       return tableModel.getColumnClass(column);
221    }
222 
223    public boolean isCellEditable(int row, int column)
224    {
225       return tableModel.isCellEditable(row, column);
226    }
227 
228    public Object getValueAt(int row, int column)
229    {
230       return tableModel.getValueAt(row, column);
231    }
232 
233    public void setValueAt(Object aValue, int row, int column)
234    {
235       tableModel.setValueAt(aValue, row, column);
236    }
237 
238    private ActionListener actionListener;
239 
240    public void addActionListener(ActionListener actionListener)
241    {
242       this.actionListener = actionListener;
243    }
244 
245    /***
246     * @return Returns the sortingColumns.
247     */
248    public List getSortingColumns()
249    {
250       return sortingColumns;
251    }
252 
253    // Helper classes
254 
255    private class TableModelHandler implements TableModelListener
256    {
257       public void tableChanged(TableModelEvent e)
258       {
259          // If we're not sorting by anything, just pass the event along.
260          if (!isSorting())
261          {
262             clearSortingState();
263             fireTableChanged(e);
264             return;
265          }
266 
267          // If the table structure has changed, cancel the sorting; the
268          // sorting columns may have been either moved or deleted from
269          // the model.
270          if (e.getFirstRow() == TableModelEvent.HEADER_ROW)
271          {
272             cancelSorting();
273             fireTableChanged(e);
274             return;
275          }
276 
277          // Something has happened to the data that may have invalidated the row order.
278          clearSortingState();
279          fireTableDataChanged();
280          return;
281       }
282    }
283 
284    private class MouseHandler extends MouseAdapter
285    {
286       public void mouseClicked(MouseEvent e)
287       {
288          JTableHeader h = (JTableHeader) e.getSource();
289          TableColumnModel columnModel = h.getColumnModel();
290          int viewColumn = columnModel.getColumnIndexAtX(e.getX());
291          int column = columnModel.getColumn(viewColumn).getModelIndex();
292          if (column != -1)
293          {
294             int status = getSortingStatus(column);
295             if (!e.isControlDown())
296             {
297                cancelSorting();
298             }
299             // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING} or
300             // {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is pressed.
301             status = status + (e.isShiftDown() ? -1 : 1);
302             status = (status + 4) % 3 - 1; // signed mod, returning {-1, 0, 1}
303             setSortingStatus(column, status);
304             if (actionListener != null)
305             {
306                actionListener.actionPerformed(new ActionEvent(e.getSource(), e.getID(), "sort", e.getModifiers()));
307             }
308          }
309       }
310    }
311 
312    private static class Arrow implements Icon
313    {
314       private boolean descending;
315 
316       private int size;
317 
318       private int priority;
319 
320       public Arrow(boolean descending, int size, int priority)
321       {
322          this.descending = descending;
323          this.size = size;
324          this.priority = priority;
325       }
326 
327       public void paintIcon(Component c, Graphics g, int x, int y)
328       {
329          Color color = c == null ? Color.GRAY : c.getBackground();
330          // In a compound sort, make each succesive triangle 20%
331          // smaller than the previous one.
332          int dx = (int) (size / 2 * Math.pow(0.8, priority));
333          int dy = descending ? dx : -dx;
334          // Align icon (roughly) with font baseline.
335          y = y + 5 * size / 6 + (descending ? -dy : 0);
336          int shift = descending ? 1 : -1;
337          g.translate(x, y);
338 
339          // Right diagonal.
340          g.setColor(color.darker());
341          g.drawLine(dx / 2, dy, 0, 0);
342          g.drawLine(dx / 2, dy + shift, 0, shift);
343 
344          // Left diagonal.
345          g.setColor(color.brighter());
346          g.drawLine(dx / 2, dy, dx, 0);
347          g.drawLine(dx / 2, dy + shift, dx, shift);
348 
349          // Horizontal line.
350          if (descending)
351          {
352             g.setColor(color.darker().darker());
353          }
354          else
355          {
356             g.setColor(color.brighter().brighter());
357          }
358          g.drawLine(dx, 0, 0, 0);
359 
360          g.setColor(color);
361          g.translate(-x, -y);
362       }
363 
364       public int getIconWidth()
365       {
366          return size;
367       }
368 
369       public int getIconHeight()
370       {
371          return size;
372       }
373    }
374 
375    private class SortableHeaderRenderer implements TableCellRenderer
376    {
377       private TableCellRenderer tableCellRenderer;
378 
379       public SortableHeaderRenderer(TableCellRenderer tableCellRenderer)
380       {
381          this.tableCellRenderer = tableCellRenderer;
382       }
383 
384       public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
385             int row, int column)
386       {
387          Component c = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
388          if (c instanceof JLabel)
389          {
390             JLabel l = (JLabel) c;
391             l.setHorizontalTextPosition(JLabel.LEFT);
392             int modelColumn = table.convertColumnIndexToModel(column);
393             l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
394          }
395          return c;
396       }
397    }
398 
399    public static class Directive
400    {
401       private int column;
402 
403       private int direction;
404 
405       public Directive(int column, int direction)
406       {
407          this.column = column;
408          this.direction = direction;
409       }
410 
411       /***
412        * @return Returns the column.
413        */
414       public int getColumn()
415       {
416          return column;
417       }
418 
419       /***
420        * @return Returns the direction.
421        */
422       public int getDirection()
423       {
424          return direction;
425       }
426    }
427 }