001    /*
002     * $Id: JXTableHeader.java,v 1.12 2006/03/15 11:58:49 kleopatra Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    package org.jdesktop.swingx;
022    
023    import java.awt.event.MouseEvent;
024    
025    import javax.swing.JComponent;
026    import javax.swing.JTable;
027    import javax.swing.SwingUtilities;
028    import javax.swing.event.MouseInputListener;
029    import javax.swing.table.JTableHeader;
030    import javax.swing.table.TableCellRenderer;
031    import javax.swing.table.TableColumn;
032    import javax.swing.table.TableColumnModel;
033    
034    import org.jdesktop.swingx.table.ColumnHeaderRenderer;
035    
036    /**
037     * TableHeader with extended functionality if associated Table is of
038     * type JXTable.<p>
039     * 
040     * The enhancements:
041     * <ul>
042     * <li> supports pluggable handler to control user interaction for sorting
043     * <li> uses ColumnHeaderRenderer which can show the sort icon
044     * <li> triggers column pack (== auto-resize to exactly fit the contents)
045     *  on double-click in resize region.
046     * </ul>
047     * 
048     * 
049     * @author Jeanette Winzenburg
050     */
051    public class JXTableHeader extends JTableHeader {
052    
053        private SortGestureRecognizer sortGestureRecognizer;
054    
055        public JXTableHeader() {
056            super();
057        }
058    
059        public JXTableHeader(TableColumnModel columnModel) {
060            super(columnModel);
061        }
062    
063        /**
064         * Sets the associated JTable. Enables enhanced header
065         * features if table is of type JXTable.<p>
066         * 
067         * PENDING: who is responsible for synching the columnModel?
068         */
069        public void setTable(JTable table) {
070            super.setTable(table);
071    //        setColumnModel(table.getColumnModel());
072            // the additional listening option makes sense only if the table
073            // actually is a JXTable
074            if (getXTable() != null) {
075                installHeaderListener();
076            } else {
077                uninstallHeaderListener();
078            }
079        }
080    
081        public JXTable getXTable() {
082            if (!(getTable() instanceof JXTable))
083                return null;
084            return (JXTable) getTable();
085        }
086    
087        
088        public void updateUI() {
089            super.updateUI();
090            if (getDefaultRenderer() instanceof JComponent) {
091                ((JComponent) getDefaultRenderer()).updateUI();
092             
093            }
094        }
095        /**
096         * returns the (visible) view index for the given column
097         * or -1 if not visible or not contained in this header's
098         * columnModel.
099         * 
100         * 
101         * @param aColumn
102         * @return
103         */
104        private int getViewIndexForColumn(TableColumn aColumn) {
105            if (aColumn == null)
106                return -1;
107            TableColumnModel cm = getColumnModel();
108            for (int column = 0; column < cm.getColumnCount(); column++) {
109                if (cm.getColumn(column) == aColumn) {
110                    return column;
111                }
112            }
113            return -1;
114        }
115    
116        protected TableCellRenderer createDefaultRenderer() {
117            return ColumnHeaderRenderer.createColumnHeaderRenderer();
118        }
119    
120        /**
121         * Lazily creates and returns the SortGestureRecognizer.
122         * 
123         * @return the SortGestureRecognizer used in Headerlistener.
124         */
125        public SortGestureRecognizer getSortGestureRecognizer() {
126            if (sortGestureRecognizer == null) {
127                sortGestureRecognizer = createSortGestureRecognizer();
128            }
129            return sortGestureRecognizer;
130            
131        }
132        
133        /**
134         * Set the SortGestureRecognizer for use in the HeaderListener.
135         * 
136         * @param recognizer the recognizer to use in HeaderListener.
137         */
138        public void setSortGestureRecognizer(SortGestureRecognizer recognizer) {
139            this.sortGestureRecognizer = recognizer;
140        }
141        
142        /**
143         * creates and returns the default SortGestureRecognizer.
144         * @return the SortGestureRecognizer used in Headerlistener.
145         * 
146         */
147        protected SortGestureRecognizer createSortGestureRecognizer() {
148            return new SortGestureRecognizer();
149        }
150    
151        protected void installHeaderListener() {
152            if (headerListener == null) {
153                headerListener = new HeaderListener();
154                addMouseListener(headerListener);
155                addMouseMotionListener(headerListener);
156    
157            }
158        }
159    
160        protected void uninstallHeaderListener() {
161            if (headerListener != null) {
162                removeMouseListener(headerListener);
163                removeMouseMotionListener(headerListener);
164                headerListener = null;
165            }
166        }
167    
168        private MouseInputListener headerListener;
169    
170        private class HeaderListener implements MouseInputListener {
171            private TableColumn cachedResizingColumn;
172    
173            public void mouseClicked(MouseEvent e) {
174                if (shouldIgnore(e)) {
175                    return;
176                }
177                if (isInResizeRegion(e)) {
178                    doResize(e);
179                } else {
180                    doSort(e);
181                }
182            }
183    
184            private boolean shouldIgnore(MouseEvent e) {
185                return !SwingUtilities.isLeftMouseButton(e)
186                  || !table.isEnabled();
187            }
188    
189            private void doSort(MouseEvent e) {
190                JXTable table = getXTable();
191                if (!table.isSortable())
192                    return;
193                if (getSortGestureRecognizer().isResetSortOrderGesture(e)) {
194                    table.resetSortOrder();
195                    repaint();
196                } else if (getSortGestureRecognizer().isToggleSortOrderGesture(e)){
197                    int column = columnAtPoint(e.getPoint());
198                    if (column >= 0) {
199                        table.toggleSortOrder(column);
200                    }
201                    uncacheResizingColumn();
202                    repaint();
203                }
204    
205            }
206    
207            private void doResize(MouseEvent e) {
208                if (e.getClickCount() != 2)
209                    return;
210                int column = getViewIndexForColumn(cachedResizingColumn);
211                if (column >= 0) {
212                    (getXTable()).packColumn(column, 5);
213                }
214                uncacheResizingColumn();
215    
216            }
217    
218    
219            public void mouseReleased(MouseEvent e) {
220                cacheResizingColumn(e);
221            }
222    
223            public void mousePressed(MouseEvent e) {
224                cacheResizingColumn(e);
225            }
226    
227            private void cacheResizingColumn(MouseEvent e) {
228                if (!getSortGestureRecognizer().isSortOrderGesture(e))
229                    return;
230                TableColumn column = getResizingColumn();
231                if (column != null) {
232                    cachedResizingColumn = column;
233                }
234            }
235    
236            private void uncacheResizingColumn() {
237                cachedResizingColumn = null;
238            }
239    
240            private boolean isInResizeRegion(MouseEvent e) {
241                return cachedResizingColumn != null; // inResize;
242            }
243    
244            public void mouseEntered(MouseEvent e) {
245            }
246    
247            public void mouseExited(MouseEvent e) {
248                uncacheResizingColumn();
249            }
250    
251            public void mouseDragged(MouseEvent e) {
252                uncacheResizingColumn();
253            }
254    
255            public void mouseMoved(MouseEvent e) {
256            }
257        }
258    
259        /**
260         * Encapsulates decision about which MouseEvents should
261         * trigger sort/unsort events.
262         * 
263         * Here: a single left click for toggling sort order, a
264         * single SHIFT-left click for unsorting.
265         * 
266         */
267        public static class SortGestureRecognizer {
268            public boolean isResetSortOrderGesture(MouseEvent e) {
269                return isSortOrderGesture(e) && isResetModifier(e);
270            }
271    
272            protected boolean isResetModifier(MouseEvent e) {
273                return ((e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) == MouseEvent.SHIFT_DOWN_MASK);
274            }
275            
276            public boolean isToggleSortOrderGesture(MouseEvent e) {
277                return isSortOrderGesture(e) && !isResetModifier(e);
278            }
279            
280            public boolean isSortOrderGesture(MouseEvent e) {
281                return e.getClickCount() == 1;
282            }
283        }
284    
285    
286    }