001 /* 002 * Copyright 2002-2004 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.richclient.table; 017 018 import java.awt.Component; 019 import java.awt.Rectangle; 020 import java.awt.SystemColor; 021 import java.awt.event.InputEvent; 022 import java.awt.event.MouseAdapter; 023 import java.awt.event.MouseEvent; 024 025 import javax.swing.Icon; 026 import javax.swing.JOptionPane; 027 import javax.swing.JTable; 028 import javax.swing.SwingConstants; 029 import javax.swing.UIManager; 030 import javax.swing.event.ChangeEvent; 031 import javax.swing.event.ListSelectionEvent; 032 import javax.swing.event.TableColumnModelEvent; 033 import javax.swing.event.TableColumnModelListener; 034 import javax.swing.table.DefaultTableCellRenderer; 035 import javax.swing.table.JTableHeader; 036 import javax.swing.table.TableColumn; 037 import javax.swing.table.TableColumnModel; 038 039 import org.springframework.richclient.core.UIConstants; 040 import org.springframework.richclient.image.ArrowIcon; 041 import org.springframework.util.Assert; 042 043 /** 044 * @author Keith Donald 045 */ 046 public class TableSortIndicator implements TableColumnModelListener { 047 public static final ArrowIcon STANDARD_UP_ARROW = new ArrowIcon(ArrowIcon.Direction.UP, 4, 048 SystemColor.controlDkShadow); 049 050 public static final ArrowIcon STANDARD_DOWN_ARROW = new ArrowIcon(ArrowIcon.Direction.DOWN, 4, 051 SystemColor.controlDkShadow); 052 053 private final TableHeaderClickHandler tableHeaderClickHandler = new TableHeaderClickHandler(); 054 055 private JTable table; 056 057 private Icon ascendingIcon; 058 059 private Icon descendingIcon; 060 061 private ColumnSortList sortList; 062 063 public TableSortIndicator(JTable table) { 064 this(table, STANDARD_UP_ARROW, STANDARD_DOWN_ARROW); 065 } 066 067 public TableSortIndicator(JTable table, Icon ascendingIcon, Icon descendingIcon) { 068 Assert.notNull(table); 069 Assert.notNull(ascendingIcon); 070 Assert.notNull(descendingIcon); 071 this.table = table; 072 this.ascendingIcon = ascendingIcon; 073 this.descendingIcon = descendingIcon; 074 sortList = new ColumnSortList(); 075 initHeaderRenderers(); 076 this.table.getColumnModel().addColumnModelListener(this); 077 this.table.getTableHeader().addMouseListener(tableHeaderClickHandler); 078 } 079 080 public ColumnSortList getColumnSortList() { 081 return sortList; 082 } 083 084 private void initHeaderRenderers() { 085 for (int i = 0; i < table.getColumnCount(); i++) { 086 TableColumn col = table.getColumnModel().getColumn(i); 087 col.setHeaderRenderer(new HeaderRenderer(table.getTableHeader())); 088 col.setPreferredWidth(TableUtils.calculatePreferredColumnWidth(table, col)); 089 col.setWidth(col.getPreferredWidth()); 090 } 091 } 092 093 private class TableHeaderClickHandler extends MouseAdapter { 094 public void mouseClicked(MouseEvent e) { 095 if (e.isMetaDown()) { 096 return; 097 } 098 int columnView = table.getColumnModel().getColumnIndexAtX(e.getX()); 099 if (columnView == -1) { 100 return; 101 } 102 // make sure mouseclick was not in resize area 103 Rectangle r = table.getTableHeader().getHeaderRect(columnView); 104 // working with a magic value of 3 here, as it is in TableHeaderUI 105 r.grow(-3, 0); 106 if (!r.contains(e.getPoint())) { 107 return; 108 } 109 110 int column = table.convertColumnIndexToModel(columnView); 111 int shiftPressed = e.getModifiers() & InputEvent.SHIFT_MASK; 112 ColumnToSort columnToSort = sortList.getSortLevel(column); 113 if (columnToSort != null) { 114 if (shiftPressed == 1) { 115 sortList.toggleSortOrder(column); 116 displayRendererIcon(column, columnToSort.getSortOrder()); 117 } 118 else { 119 SortOrder order; 120 if (sortList.size() > 1) { 121 order = SortOrder.ASCENDING; 122 } 123 else { 124 order = columnToSort.getSortOrder().flip(); 125 } 126 sortList.setSingleSortLevel(column, order); 127 removeRendererIcons(); 128 displayRendererIcon(columnView, order); 129 } 130 } 131 else { 132 if (shiftPressed == 1) { 133 try { 134 sortList.addSortLevel(column, SortOrder.ASCENDING); 135 displayRendererIcon(columnView, SortOrder.ASCENDING); 136 } 137 catch (IllegalArgumentException ex) { 138 JOptionPane.showMessageDialog(table.getTopLevelAncestor(), 139 "Maximum number of sort levels reached.", "Table Sorter", JOptionPane.WARNING_MESSAGE); 140 return; 141 } 142 } 143 else { 144 sortList.setSingleSortLevel(column, SortOrder.ASCENDING); 145 removeRendererIcons(); 146 displayRendererIcon(columnView, SortOrder.ASCENDING); 147 } 148 } 149 table.getTableHeader().resizeAndRepaint(); 150 } 151 } 152 153 private static final class HeaderRenderer extends DefaultTableCellRenderer { 154 private JTableHeader tableHeader; 155 156 public HeaderRenderer(JTableHeader header) { 157 setForeground(header.getForeground()); 158 setBackground(header.getBackground()); 159 setBorder(UIManager.getBorder("TableHeader.cellBorder")); 160 setIconTextGap(UIConstants.TWO_SPACES); 161 setHorizontalTextPosition(SwingConstants.LEFT); 162 this.tableHeader = header; 163 } 164 165 public Component getTableCellRendererComponent(JTable table, Object value, boolean iSelected, boolean hasFocus, 166 int row, int column) { 167 setText((value == null) ? "" : value.toString()); 168 setFont(tableHeader.getFont()); 169 return this; 170 } 171 } 172 173 private void displayRendererIcon(int column, SortOrder order) { 174 HeaderRenderer rend = getRenderer(column); 175 if (order == SortOrder.ASCENDING) { 176 rend.setIcon(this.ascendingIcon); 177 } 178 else { 179 rend.setIcon(this.descendingIcon); 180 } 181 } 182 183 private void removeRendererIcons() { 184 TableColumnModel colModel = table.getColumnModel(); 185 for (int i = 0; i < colModel.getColumnCount(); i++) { 186 HeaderRenderer rend = (HeaderRenderer) colModel.getColumn(i).getHeaderRenderer(); 187 rend.setIcon(null); 188 } 189 } 190 191 private HeaderRenderer getRenderer(int columnIndex) { 192 TableColumn column = table.getColumnModel().getColumn(columnIndex); 193 return (HeaderRenderer) column.getHeaderRenderer(); 194 } 195 196 public void columnAdded(TableColumnModelEvent e) { 197 TableColumn column = table.getColumnModel().getColumn(e.getToIndex()); 198 if (column.getHeaderRenderer() instanceof HeaderRenderer) { 199 ((HeaderRenderer)column.getHeaderRenderer()).setIcon(null); 200 } 201 else { 202 column.setHeaderRenderer(new HeaderRenderer(table.getTableHeader())); 203 } 204 } 205 206 public void columnMarginChanged(ChangeEvent e) { 207 } 208 209 public void columnMoved(TableColumnModelEvent e) { 210 } 211 212 public void columnRemoved(TableColumnModelEvent e) { 213 } 214 215 public void columnSelectionChanged(ListSelectionEvent e) { 216 } 217 }