001: /* ====================================================================
002: * The QueryForm License, Version 1.1
003: *
004: * Copyright (c) 1998 - 2003 David F. Glasser. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution,
020: * if any, must include the following acknowledgment:
021: * "This product includes software developed by
022: * David F. Glasser."
023: * Alternately, this acknowledgment may appear in the software itself,
024: * if and wherever such third-party acknowledgments normally appear.
025: *
026: * 4. The names "QueryForm" and "David F. Glasser" must
027: * not be used to endorse or promote products derived from this
028: * software without prior written permission. For written
029: * permission, please contact dglasser@pobox.com.
030: *
031: * 5. Products derived from this software may not be called "QueryForm",
032: * nor may "QueryForm" appear in their name, without prior written
033: * permission of David F. Glasser.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL DAVID F. GLASSER, THE APACHE SOFTWARE
039: * FOUNDATION OR ITS CONTRIBUTORS, OR ANY AUTHORS OR DISTRIBUTORS
040: * OF THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This product includes software developed by the
051: * Apache Software Foundation (http://www.apache.org/).
052: *
053: * ====================================================================
054: *
055: * $Source: /cvsroot/qform/qform/src/org/glasser/swing/table/PushButtonTableHeader.java,v $
056: * $Revision: 1.3 $
057: * $Author: dglasser $
058: * $Date: 2003/07/13 04:35:40 $
059: *
060: * --------------------------------------------------------------------
061: */
062: package org.glasser.swing.table;
063:
064: import javax.swing.*;
065: import javax.swing.table.*;
066: import java.awt.*;
067: import java.awt.event.*;
068: import org.glasser.swing.BevelArrowIcon;
069:
070: /**
071: * This is a JTableHeader subclass that uses a JButton to paint the column
072: * headers on a JTable. It also keeps track of a sorted column and a sort order
073: * which it can use to paint up or down arrow icons on the header of a sorted column.
074: * The main() method of this class invokes a small sample program that demonstrates
075: * the use of this class.
076: *
077: * @author David F. Glasser
078: */
079: public class PushButtonTableHeader extends JTableHeader {
080:
081: protected int sortedColumn = -1;
082:
083: protected boolean descendingSort = false;
084:
085: protected int pressedColumn = -1;
086:
087: public final static double DEFAULT_ICON_SIZE = 10.0;
088:
089: public final static Icon DEFAULT_UP_ICON = new BevelArrowIcon(
090: DEFAULT_ICON_SIZE, BevelArrowIcon.UP);
091:
092: public final static Icon DEFAULT_DOWN_ICON = new BevelArrowIcon(
093: DEFAULT_ICON_SIZE, BevelArrowIcon.DOWN);
094:
095: /**
096: * This is the JButton used to paint the column headers.
097: */
098: protected JButton rendererButton = new JButton();
099:
100: /**
101: * This is the icon used to indicate that a column has been sorted in ascending order.
102: * The default value is the DEFAULT_DOWN_ICON.
103: */
104: protected Icon upIcon = DEFAULT_UP_ICON;
105:
106: /**
107: * This is the icon used to indicate that a column has been sorted in descending order.
108: * The default value is the DEFAULT_DOWN_ICON.
109: */
110: protected Icon downIcon = DEFAULT_DOWN_ICON;
111:
112: public PushButtonTableHeader() {
113: this (null);
114: }
115:
116: public PushButtonTableHeader(TableColumnModel tcm) {
117: super (tcm);
118:
119: rendererButton.setFont(this .getFont());
120: rendererButton.setForeground(this .getForeground());
121: rendererButton.setMargin(new Insets(0, 0, 0, 0));
122: rendererButton.setHorizontalTextPosition(JButton.LEFT);
123:
124: // this prevents the empty space that normally surrounds a JButton under
125: // the Motif look and feel.
126: rendererButton.setDefaultCapable(false);
127:
128: // the mouse listener added here will manage the "pushbutton" appearance
129: // of the column headers.
130: this .addMouseListener(new MouseAdapter() {
131: /**
132: * Invoked when a mouse button has been pressed on a component.
133: */
134: public void mousePressed(MouseEvent e) {
135:
136: int modifiers = e.getModifiers();
137: if ((modifiers & e.BUTTON1_MASK) == 0)
138: return;
139:
140: // if the user has clicked on the border between two columns
141: // for the purpose of resizing one of them, this call will return non-null.
142: TableColumn tc = getResizingColumn();
143:
144: // don't sort if the user's only trying to resize a column
145: if (tc != null)
146: return;
147:
148: pressedColumn = columnAtPoint(e.getPoint());
149: JTable table = getTable();
150: if (table != null) {
151: // always store the pressedColumn index in terms of the column model
152: // instead of the view, because the view can change as the user
153: // drags columns around.
154: pressedColumn = table
155: .convertColumnIndexToModel(pressedColumn);
156: }
157:
158: PushButtonTableHeader.this .repaint();
159:
160: }
161:
162: /**
163: * Invoked when a mouse button has been released on a component.
164: */
165: public void mouseReleased(MouseEvent e) {
166: pressedColumn = -1;
167: PushButtonTableHeader.this .repaint();
168: }
169: });
170:
171: }
172:
173: public void setTable(JTable table) {
174: super .setTable(table);
175: this .setColumnModel(table.getColumnModel());
176: }
177:
178: /**
179: * This method tells a PushButtonTableHeader which column should have
180: * the arrow icon painted on it to indicate it is the sorted column, and
181: * whether the arrow should point up or down.
182: *
183: * @param the VIEW index of the sortedColumn. It is important that the sortedColumn
184: * parameter is the view-based index rather than the model-based index. The view-based index
185: * will be the index returned by the JTableHeader.columnAtPoint() method. If an out-of-range
186: * index is passed in (for instance, -1) then no arrow icons will be drawn. This
187: * can be used to remove the arrows from an unsorted table.
188: *
189: * @param descendingSort if true, the downIcon (arrow pointing down) will be painted on the
190: * sorted column header; if false the upIcon (arrow pointing up) will be used.
191: */
192: public void setSortedColumn(int columnIndex, boolean descendingSort) {
193:
194: JTable table = getTable();
195: if (table != null) {
196: // always store the sortedColumn index in terms of the column model
197: // instead of the view, because the view can change as the user
198: // drags columns around.
199: sortedColumn = table.convertColumnIndexToModel(columnIndex);
200: } else {
201: sortedColumn = columnIndex;
202: }
203: this .descendingSort = descendingSort;
204:
205: repaint();
206: }
207:
208: /**
209: * Sets the icon used to indicate that a column has been sorted in ascending order.
210: * The default value is the DEFAULT_DOWN_ICON.
211: */
212: public void setUpIcon(Icon upIcon) {
213: this .upIcon = upIcon;
214: }
215:
216: /**
217: * Sets the icon used to indicate that a column has been sorted in descending order.
218: * The default value is the DEFAULT_DOWN_ICON.
219: */
220: public void setDownIcon(Icon downIcon) {
221: this .downIcon = downIcon;
222: }
223:
224: protected class PushButtonRenderer implements TableCellRenderer {
225:
226: /**
227: * Returns the component used for drawing the cell. This method is
228: * used to configure the renderer appropriately before drawing.
229: *
230: * @param table the <code>JTable</code> that is asking the
231: * renderer to draw; can be <code>null</code>
232: * @param value the value of the cell to be rendered. It is
233: * up to the specific renderer to interpret
234: * and draw the value. For example, if
235: * <code>value</code>
236: * is the string "true", it could be rendered as a
237: * string or it could be rendered as a check
238: * box that is checked. <code>null</code> is a
239: * valid value
240: * @param isSelected true if the cell is to be rendered with the
241: * selection highlighted; otherwise false
242: * @param hasFocus if true, render cell appropriately. For
243: * example, put a special border on the cell, if
244: * the cell can be edited, render in the color used
245: * to indicate editing
246: * @param row the row index of the cell being drawn. When
247: * drawing the header, the value of
248: * <code>row</code> is -1
249: * @param column the column index of the cell being drawn
250: */
251: public Component getTableCellRendererComponent(JTable table,
252: Object value, boolean isSelected, boolean hasFocus,
253: int row, int column) {
254:
255: int actualColumn = table.convertColumnIndexToModel(column);
256:
257: if (actualColumn == sortedColumn) {
258: if (descendingSort) {
259: rendererButton.setIcon(downIcon);
260: } else {
261: rendererButton.setIcon(upIcon);
262: }
263: } else {
264: rendererButton.setIcon(null);
265: }
266:
267: boolean isPressed = actualColumn == pressedColumn;
268: rendererButton.getModel().setArmed(isPressed);
269: rendererButton.getModel().setPressed(isPressed);
270:
271: rendererButton.setText(value == null ? "" : value
272: .toString());
273:
274: return rendererButton;
275:
276: }
277: }
278:
279: /**
280: * This is the TableCellRenderer used to paint the column headers.
281: */
282: protected PushButtonRenderer renderer = new PushButtonRenderer();
283:
284: /**
285: * Returns the default renderer used when no <code>headerRenderer</code>
286: * is defined by a <code>TableColumn</code>.
287: * @return the default renderer
288: */
289: public TableCellRenderer getDefaultRenderer() {
290: return renderer;
291: }
292:
293: /**
294: * Notification from the <code>UIManager</code> that the look and feel
295: * (L&F) has changed.
296: * Replaces the current UI object with the latest version from the
297: * <code>UIManager</code>.
298: *
299: * @see JComponent#updateUI
300: */
301: public void updateUI() {
302: super .updateUI();
303: if (rendererButton != null)
304: rendererButton.updateUI();
305: }
306:
307: /**
308: * Invokes a small sample program that demonstrates how this
309: * class is used.
310: */
311: public static void main(String[] args) throws Exception {
312:
313: System.out.println("Java version: "
314: + System.getProperty("java.version"));
315:
316: JFrame frame = new JFrame("PushButtonTableHeader Demo");
317: JPanel cp = new JPanel();
318: cp.setLayout(new BorderLayout());
319: JTable table = new JTable();
320: table.setModel(new DefaultTableModel(50, 5));
321: PushButtonTableHeader header = new PushButtonTableHeader();
322: table.setTableHeader(header);
323:
324: //////////////////////////////////////////////////////////////////////////
325: // This code is only necessary when the Java Runtime Environment is prior
326: // to version 1.3. To be on the safe side, it should probably always be
327: // included, because it doesn't hurt anything on versions 1.3 or 1.4.
328: TableColumnModel tcm = table.getColumnModel();
329: for (int j = 0; j < tcm.getColumnCount(); j++) {
330: TableColumn tc = tcm.getColumn(j);
331: tc.setHeaderRenderer(header.getDefaultRenderer());
332: }
333: //////////////////////////////////////////////////////////////////////////
334:
335: header.addMouseListener(new MouseAdapter() {
336:
337: int sortedColumn = -1;
338:
339: boolean descendingSort = false;
340:
341: /**
342: * Invoked when a mouse button has been pressed on a component.
343: */
344: public void mousePressed(MouseEvent e) {
345:
346: int modifiers = e.getModifiers();
347: if ((modifiers & e.BUTTON1_MASK) == 0)
348: return;
349:
350: PushButtonTableHeader header = (PushButtonTableHeader) e
351: .getSource();
352:
353: // if the user has clicked on the border between two columns
354: // for the purpose of resizing one of them, this call will return non-null.
355: TableColumn tc = header.getResizingColumn();
356:
357: // don't sort if the user's only trying to resize a column
358: if (tc != null)
359: return;
360:
361: int viewColumn = header.columnAtPoint(e.getPoint());
362:
363: // translate the view column index to the model column index
364: int modelColumn = header.getTable()
365: .convertColumnIndexToModel(viewColumn);
366:
367: // if the clicked column is already sorted, reverse the
368: // current sort order
369: if (modelColumn == sortedColumn) {
370: descendingSort = !descendingSort;
371: } else {
372: // the first time a column is clicked the
373: // sort order is ascending.
374: descendingSort = false;
375: }
376:
377: // remember the currently sorted column in terms of
378: // the model, not the view
379: sortedColumn = modelColumn;
380:
381: //////////////////////////////////////////////////////
382: // a method call to sort the table would go here
383: //////////////////////////////////////////////////////
384:
385: // set the sortedColumn on the header so it knows how to draw the arrow.
386: // Notice that we pass in the column index that's based on the view, not
387: // the model, because the header will tranlate it internally.
388: header.setSortedColumn(viewColumn, descendingSort);
389: }
390: });
391:
392: cp.add(new JScrollPane(table), BorderLayout.CENTER);
393: frame.setContentPane(cp);
394: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
395: frame.pack();
396: frame.setVisible(true);
397: }
398: }
|