1 /*
2  * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.swing.plaf.basic;
27 
28 import java.awt.*;
29 import java.awt.event.*;
30 import java.util.*;
31 import javax.swing.*;
32 import javax.swing.event.*;
33 import javax.swing.plaf.*;
34 import javax.swing.table.*;
35 
36 import sun.swing.*;
37 
38 /**
39  * BasicTableHeaderUI implementation
40  *
41  * @author Alan Chung
42  * @author Philip Milne
43  */
44 public class BasicTableHeaderUI extends TableHeaderUI {
45 
46     private static Cursor resizeCursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
47 
48 //
49 // Instance Variables
50 //
51 
52     /** The JTableHeader that is delegating the painting to this UI. */
53     protected JTableHeader header;
54     protected CellRendererPane rendererPane;
55 
56     // Listeners that are attached to the JTable
57     protected MouseInputListener mouseInputListener;
58 
59     // The column header over which the mouse currently is.
60     private int rolloverColumn = -1;
61 
62     // The column that should be highlighted when the table header has the focus.
63     private int selectedColumnIndex = 0; // Read ONLY via getSelectedColumnIndex!
64 
65     private static FocusListener focusListener = new FocusListener() {
66         public void focusGained(FocusEvent e) {
67             repaintHeader(e.getSource());
68         }
69 
70         public void focusLost(FocusEvent e) {
71             repaintHeader(e.getSource());
72         }
73 
74         private void repaintHeader(Object source) {
75             if (source instanceof JTableHeader) {
76                 JTableHeader th = (JTableHeader)source;
77                 BasicTableHeaderUI ui =
78                    (BasicTableHeaderUI)BasicLookAndFeel.
79                                         getUIOfType(th.getUI(),
80                                             BasicTableHeaderUI.class);
81                 if (ui == null) {
82                     return;
83                 }
84 
85                 th.repaint(th.getHeaderRect(ui.getSelectedColumnIndex()));
86             }
87         }
88     };
89 
90     /**
91      * This class should be treated as a "protected" inner class.
92      * Instantiate it only within subclasses of {@code BasicTableHeaderUI}.
93      */
94     public class MouseInputHandler implements MouseInputListener {
95 
96         private int mouseXOffset;
97         private Cursor otherCursor = resizeCursor;
98 
mouseClicked(MouseEvent e)99         public void mouseClicked(MouseEvent e) {
100             if (!header.isEnabled()) {
101                 return;
102             }
103             if (e.getClickCount() % 2 == 1 &&
104                     SwingUtilities.isLeftMouseButton(e)) {
105                 JTable table = header.getTable();
106                 RowSorter sorter;
107                 if (table != null && (sorter = table.getRowSorter()) != null) {
108                     int columnIndex = header.columnAtPoint(e.getPoint());
109                     if (columnIndex != -1) {
110                         columnIndex = table.convertColumnIndexToModel(
111                                 columnIndex);
112                         sorter.toggleSortOrder(columnIndex);
113                     }
114                 }
115             }
116         }
117 
getResizingColumn(Point p)118         private TableColumn getResizingColumn(Point p) {
119             return getResizingColumn(p, header.columnAtPoint(p));
120         }
121 
getResizingColumn(Point p, int column)122         private TableColumn getResizingColumn(Point p, int column) {
123             if (column == -1) {
124                  return null;
125             }
126             Rectangle r = header.getHeaderRect(column);
127             r.grow(-3, 0);
128             if (r.contains(p)) {
129                 return null;
130             }
131             int midPoint = r.x + r.width/2;
132             int columnIndex;
133             if( header.getComponentOrientation().isLeftToRight() ) {
134                 columnIndex = (p.x < midPoint) ? column - 1 : column;
135             } else {
136                 columnIndex = (p.x < midPoint) ? column : column - 1;
137             }
138             if (columnIndex == -1) {
139                 return null;
140             }
141             return header.getColumnModel().getColumn(columnIndex);
142         }
143 
mousePressed(MouseEvent e)144         public void mousePressed(MouseEvent e) {
145             if (!header.isEnabled()) {
146                 return;
147             }
148             header.setDraggedColumn(null);
149             header.setResizingColumn(null);
150             header.setDraggedDistance(0);
151 
152             Point p = e.getPoint();
153 
154             // First find which header cell was hit
155             TableColumnModel columnModel = header.getColumnModel();
156             int index = header.columnAtPoint(p);
157 
158             if (index != -1) {
159                 // The last 3 pixels + 3 pixels of next column are for resizing
160                 TableColumn resizingColumn = getResizingColumn(p, index);
161                 if (canResize(resizingColumn, header)) {
162                     header.setResizingColumn(resizingColumn);
163                     if( header.getComponentOrientation().isLeftToRight() ) {
164                         mouseXOffset = p.x - resizingColumn.getWidth();
165                     } else {
166                         mouseXOffset = p.x + resizingColumn.getWidth();
167                     }
168                 }
169                 else if (header.getReorderingAllowed()) {
170                     TableColumn hitColumn = columnModel.getColumn(index);
171                     header.setDraggedColumn(hitColumn);
172                     mouseXOffset = p.x;
173                 }
174             }
175 
176             if (header.getReorderingAllowed()) {
177                 int oldRolloverColumn = rolloverColumn;
178                 rolloverColumn = -1;
179                 rolloverColumnUpdated(oldRolloverColumn, rolloverColumn);
180             }
181         }
182 
swapCursor()183         private void swapCursor() {
184             Cursor tmp = header.getCursor();
185             header.setCursor(otherCursor);
186             otherCursor = tmp;
187         }
188 
mouseMoved(MouseEvent e)189         public void mouseMoved(MouseEvent e) {
190             if (!header.isEnabled()) {
191                 return;
192             }
193             if (canResize(getResizingColumn(e.getPoint()), header) !=
194                 (header.getCursor() == resizeCursor)) {
195                 swapCursor();
196             }
197             updateRolloverColumn(e);
198        }
199 
mouseDragged(MouseEvent e)200         public void mouseDragged(MouseEvent e) {
201             if (!header.isEnabled()) {
202                 return;
203             }
204             int mouseX = e.getX();
205 
206             TableColumn resizingColumn  = header.getResizingColumn();
207             TableColumn draggedColumn  = header.getDraggedColumn();
208 
209             boolean headerLeftToRight = header.getComponentOrientation().isLeftToRight();
210 
211             if (resizingColumn != null) {
212                 int oldWidth = resizingColumn.getWidth();
213                 int newWidth;
214                 if (headerLeftToRight) {
215                     newWidth = mouseX - mouseXOffset;
216                 } else  {
217                     newWidth = mouseXOffset - mouseX;
218                 }
219                 mouseXOffset += changeColumnWidth(resizingColumn, header,
220                                                   oldWidth, newWidth);
221             }
222             else if (draggedColumn != null) {
223                 TableColumnModel cm = header.getColumnModel();
224                 int draggedDistance = mouseX - mouseXOffset;
225                 int direction = (draggedDistance < 0) ? -1 : 1;
226                 int columnIndex = viewIndexForColumn(draggedColumn);
227                 int newColumnIndex = columnIndex + (headerLeftToRight ? direction : -direction);
228                 if (0 <= newColumnIndex && newColumnIndex < cm.getColumnCount()) {
229                     int width = cm.getColumn(newColumnIndex).getWidth();
230                     if (Math.abs(draggedDistance) > (width / 2)) {
231 
232                         mouseXOffset = mouseXOffset + direction * width;
233                         header.setDraggedDistance(draggedDistance - direction * width);
234 
235                         //Cache the selected column.
236                         int selectedIndex =
237                                 SwingUtilities2.convertColumnIndexToModel(
238                                         header.getColumnModel(),
239                                         getSelectedColumnIndex());
240 
241                         //Now do the move.
242                         cm.moveColumn(columnIndex, newColumnIndex);
243 
244                         //Update the selected index.
245                         selectColumn(
246                             SwingUtilities2.convertColumnIndexToView(
247                                     header.getColumnModel(), selectedIndex),
248                             false);
249 
250                         return;
251                     }
252                 }
253                 setDraggedDistance(draggedDistance, columnIndex);
254             }
255 
256             updateRolloverColumn(e);
257         }
258 
mouseReleased(MouseEvent e)259         public void mouseReleased(MouseEvent e) {
260             if (!header.isEnabled()) {
261                 return;
262             }
263             setDraggedDistance(0, viewIndexForColumn(header.getDraggedColumn()));
264 
265             header.setResizingColumn(null);
266             header.setDraggedColumn(null);
267 
268             updateRolloverColumn(e);
269         }
270 
mouseEntered(MouseEvent e)271         public void mouseEntered(MouseEvent e) {
272             if (!header.isEnabled()) {
273                 return;
274             }
275             updateRolloverColumn(e);
276         }
277 
mouseExited(MouseEvent e)278         public void mouseExited(MouseEvent e) {
279             if (!header.isEnabled()) {
280                 return;
281             }
282             int oldRolloverColumn = rolloverColumn;
283             rolloverColumn = -1;
284             rolloverColumnUpdated(oldRolloverColumn, rolloverColumn);
285         }
286 //
287 // Protected & Private Methods
288 //
289 
setDraggedDistance(int draggedDistance, int column)290         private void setDraggedDistance(int draggedDistance, int column) {
291             header.setDraggedDistance(draggedDistance);
292             if (column != -1) {
293                 header.getColumnModel().moveColumn(column, column);
294             }
295         }
296     }
297 
298 //
299 //  Factory methods for the Listeners
300 //
301 
302     /**
303      * Creates the mouse listener for the JTableHeader.
304      */
createMouseInputListener()305     protected MouseInputListener createMouseInputListener() {
306         return new MouseInputHandler();
307     }
308 
309 //
310 //  The installation/uninstall procedures and support
311 //
312 
createUI(JComponent h)313     public static ComponentUI createUI(JComponent h) {
314         return new BasicTableHeaderUI();
315     }
316 
317 //  Installation
318 
installUI(JComponent c)319     public void installUI(JComponent c) {
320         header = (JTableHeader)c;
321 
322         rendererPane = new CellRendererPane();
323         header.add(rendererPane);
324 
325         installDefaults();
326         installListeners();
327         installKeyboardActions();
328     }
329 
330     /**
331      * Initializes JTableHeader properties such as font, foreground, and background.
332      * The font, foreground, and background properties are only set if their
333      * current value is either null or a UIResource, other properties are set
334      * if the current value is null.
335      *
336      * @see #installUI
337      */
installDefaults()338     protected void installDefaults() {
339         LookAndFeel.installColorsAndFont(header, "TableHeader.background",
340                                          "TableHeader.foreground", "TableHeader.font");
341         LookAndFeel.installProperty(header, "opaque", Boolean.TRUE);
342     }
343 
344     /**
345      * Attaches listeners to the JTableHeader.
346      */
installListeners()347     protected void installListeners() {
348         mouseInputListener = createMouseInputListener();
349 
350         header.addMouseListener(mouseInputListener);
351         header.addMouseMotionListener(mouseInputListener);
352         header.addFocusListener(focusListener);
353     }
354 
355     /**
356      * Register all keyboard actions on the JTableHeader.
357      */
installKeyboardActions()358     protected void installKeyboardActions() {
359         InputMap keyMap = (InputMap)DefaultLookup.get(header, this,
360                 "TableHeader.ancestorInputMap");
361         SwingUtilities.replaceUIInputMap(header,
362                                 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap);
363         LazyActionMap.installLazyActionMap(header, BasicTableHeaderUI.class,
364                 "TableHeader.actionMap");
365     }
366 
367 // Uninstall methods
368 
uninstallUI(JComponent c)369     public void uninstallUI(JComponent c) {
370         uninstallDefaults();
371         uninstallListeners();
372         uninstallKeyboardActions();
373 
374         header.remove(rendererPane);
375         rendererPane = null;
376         header = null;
377     }
378 
uninstallDefaults()379     protected void uninstallDefaults() {}
380 
uninstallListeners()381     protected void uninstallListeners() {
382         header.removeMouseListener(mouseInputListener);
383         header.removeMouseMotionListener(mouseInputListener);
384 
385         mouseInputListener = null;
386     }
387 
388     /**
389      * Unregisters default key actions.
390      */
uninstallKeyboardActions()391     protected void uninstallKeyboardActions() {
392         SwingUtilities.replaceUIInputMap(header, JComponent.WHEN_FOCUSED, null);
393         SwingUtilities.replaceUIActionMap(header, null);
394     }
395 
396     /**
397      * Populates TableHeader's actions.
398      */
loadActionMap(LazyActionMap map)399     static void loadActionMap(LazyActionMap map) {
400         map.put(new Actions(Actions.TOGGLE_SORT_ORDER));
401         map.put(new Actions(Actions.SELECT_COLUMN_TO_LEFT));
402         map.put(new Actions(Actions.SELECT_COLUMN_TO_RIGHT));
403         map.put(new Actions(Actions.MOVE_COLUMN_LEFT));
404         map.put(new Actions(Actions.MOVE_COLUMN_RIGHT));
405         map.put(new Actions(Actions.RESIZE_LEFT));
406         map.put(new Actions(Actions.RESIZE_RIGHT));
407         map.put(new Actions(Actions.FOCUS_TABLE));
408     }
409 
410 //
411 // Support for mouse rollover
412 //
413 
414     /**
415      * Returns the index of the column header over which the mouse
416      * currently is. When the mouse is not over the table header,
417      * -1 is returned.
418      *
419      * @see #rolloverColumnUpdated(int, int)
420      * @return the index of the current rollover column
421      * @since 1.6
422      */
getRolloverColumn()423     protected int getRolloverColumn() {
424         return rolloverColumn;
425     }
426 
427     /**
428      * This method gets called every time when a rollover column in the table
429      * header is updated. Every look and feel that supports a rollover effect
430      * in a table header should override this method and repaint the header.
431      *
432      * @param oldColumn the index of the previous rollover column or -1 if the
433      * mouse was not over a column
434      * @param newColumn the index of the new rollover column or -1 if the mouse
435      * is not over a column
436      * @see #getRolloverColumn()
437      * @see JTableHeader#getHeaderRect(int)
438      * @since 1.6
439      */
rolloverColumnUpdated(int oldColumn, int newColumn)440     protected void rolloverColumnUpdated(int oldColumn, int newColumn) {
441     }
442 
updateRolloverColumn(MouseEvent e)443     private void updateRolloverColumn(MouseEvent e) {
444         if (header.getDraggedColumn() == null &&
445             header.contains(e.getPoint())) {
446 
447             int col = header.columnAtPoint(e.getPoint());
448             if (col != rolloverColumn) {
449                 int oldRolloverColumn = rolloverColumn;
450                 rolloverColumn = col;
451                 rolloverColumnUpdated(oldRolloverColumn, rolloverColumn);
452             }
453         }
454     }
455 
456 //
457 // Support for keyboard and mouse access
458 //
selectNextColumn(boolean doIt)459     private int selectNextColumn(boolean doIt) {
460         int newIndex = getSelectedColumnIndex();
461         if (newIndex < header.getColumnModel().getColumnCount() - 1) {
462             newIndex++;
463             if (doIt) {
464                 selectColumn(newIndex);
465             }
466         }
467         return newIndex;
468     }
469 
selectPreviousColumn(boolean doIt)470     private int selectPreviousColumn(boolean doIt) {
471         int newIndex = getSelectedColumnIndex();
472         if (newIndex > 0) {
473             newIndex--;
474             if (doIt) {
475                 selectColumn(newIndex);
476             }
477         }
478         return newIndex;
479     }
480 
481     /**
482      * Selects the specified column in the table header. Repaints the
483      * affected header cells and makes sure the newly selected one is visible.
484      */
selectColumn(int newColIndex)485     void selectColumn(int newColIndex) {
486         selectColumn(newColIndex, true);
487     }
488 
selectColumn(int newColIndex, boolean doScroll)489     void selectColumn(int newColIndex, boolean doScroll) {
490         Rectangle repaintRect = header.getHeaderRect(selectedColumnIndex);
491         header.repaint(repaintRect);
492         selectedColumnIndex = newColIndex;
493         repaintRect = header.getHeaderRect(newColIndex);
494         header.repaint(repaintRect);
495         if (doScroll) {
496             scrollToColumn(newColIndex);
497         }
498         return;
499     }
500     /**
501      * Used by selectColumn to scroll horizontally, if necessary,
502      * to ensure that the newly selected column is visible.
503      */
scrollToColumn(int col)504     private void scrollToColumn(int col) {
505         Container container;
506         JTable table;
507 
508         //Test whether the header is in a scroll pane and has a table.
509         if ((header.getParent() == null) ||
510             ((container = header.getParent().getParent()) == null) ||
511             !(container instanceof JScrollPane) ||
512             ((table = header.getTable()) == null)) {
513             return;
514         }
515 
516         //Now scroll, if necessary.
517         Rectangle vis = table.getVisibleRect();
518         Rectangle cellBounds = table.getCellRect(0, col, true);
519         vis.x = cellBounds.x;
520         vis.width = cellBounds.width;
521         table.scrollRectToVisible(vis);
522     }
523 
getSelectedColumnIndex()524     private int getSelectedColumnIndex() {
525         int numCols = header.getColumnModel().getColumnCount();
526         if (selectedColumnIndex >= numCols && numCols > 0) {
527             selectedColumnIndex = numCols - 1;
528         }
529         return selectedColumnIndex;
530     }
531 
canResize(TableColumn column, JTableHeader header)532     private static boolean canResize(TableColumn column,
533                                      JTableHeader header) {
534         return (column != null) && header.getResizingAllowed()
535                                 && column.getResizable();
536     }
537 
changeColumnWidth(TableColumn resizingColumn, JTableHeader th, int oldWidth, int newWidth)538     private int changeColumnWidth(TableColumn resizingColumn,
539                                   JTableHeader th,
540                                   int oldWidth, int newWidth) {
541         resizingColumn.setWidth(newWidth);
542 
543         Container container;
544         JTable table;
545 
546         if ((th.getParent() == null) ||
547             ((container = th.getParent().getParent()) == null) ||
548             !(container instanceof JScrollPane) ||
549             ((table = th.getTable()) == null)) {
550             return 0;
551         }
552 
553         if (!container.getComponentOrientation().isLeftToRight() &&
554                 !th.getComponentOrientation().isLeftToRight()) {
555                 JViewport viewport = ((JScrollPane)container).getViewport();
556                 int viewportWidth = viewport.getWidth();
557                 int diff = newWidth - oldWidth;
558                 int newHeaderWidth = table.getWidth() + diff;
559 
560                 /* Resize a table */
561                 Dimension tableSize = table.getSize();
562                 tableSize.width += diff;
563                 table.setSize(tableSize);
564 
565                 /* If this table is in AUTO_RESIZE_OFF mode and
566                  * has a horizontal scrollbar, we need to update
567                  * a view's position.
568                  */
569                 if ((newHeaderWidth >= viewportWidth) &&
570                     (table.getAutoResizeMode() == JTable.AUTO_RESIZE_OFF)) {
571                     Point p = viewport.getViewPosition();
572                     p.x = Math.max(0, Math.min(newHeaderWidth - viewportWidth,
573                                                p.x + diff));
574                     viewport.setViewPosition(p);
575                     return diff;
576             }
577         }
578         return 0;
579     }
580 
581 //
582 // Baseline
583 //
584 
585     /**
586      * Returns the baseline.
587      *
588      * @throws NullPointerException {@inheritDoc}
589      * @throws IllegalArgumentException {@inheritDoc}
590      * @see javax.swing.JComponent#getBaseline(int, int)
591      * @since 1.6
592      */
getBaseline(JComponent c, int width, int height)593     public int getBaseline(JComponent c, int width, int height) {
594         super.getBaseline(c, width, height);
595         int baseline = -1;
596         TableColumnModel columnModel = header.getColumnModel();
597         for(int column = 0; column < columnModel.getColumnCount();
598             column++) {
599             TableColumn aColumn = columnModel.getColumn(column);
600             Component comp = getHeaderRenderer(column);
601             Dimension pref = comp.getPreferredSize();
602             int columnBaseline = comp.getBaseline(pref.width, height);
603             if (columnBaseline >= 0) {
604                 if (baseline == -1) {
605                     baseline = columnBaseline;
606                 }
607                 else if (baseline != columnBaseline) {
608                     baseline = -1;
609                     break;
610                 }
611             }
612         }
613         return baseline;
614     }
615 
616 //
617 // Paint Methods and support
618 //
619 
paint(Graphics g, JComponent c)620     public void paint(Graphics g, JComponent c) {
621         if (header.getColumnModel().getColumnCount() <= 0) {
622             return;
623         }
624         boolean ltr = header.getComponentOrientation().isLeftToRight();
625 
626         Rectangle clip = g.getClipBounds();
627         Point left = clip.getLocation();
628         Point right = new Point( clip.x + clip.width - 1, clip.y );
629         TableColumnModel cm = header.getColumnModel();
630         int cMin = header.columnAtPoint( ltr ? left : right );
631         int cMax = header.columnAtPoint( ltr ? right : left );
632         // This should never happen.
633         if (cMin == -1) {
634             cMin =  0;
635         }
636         // If the table does not have enough columns to fill the view we'll get -1.
637         // Replace this with the index of the last column.
638         if (cMax == -1) {
639             cMax = cm.getColumnCount()-1;
640         }
641 
642         TableColumn draggedColumn = header.getDraggedColumn();
643         int columnWidth;
644         Rectangle cellRect = header.getHeaderRect(ltr ? cMin : cMax);
645         TableColumn aColumn;
646         if (ltr) {
647             for(int column = cMin; column <= cMax ; column++) {
648                 aColumn = cm.getColumn(column);
649                 columnWidth = aColumn.getWidth();
650                 cellRect.width = columnWidth;
651                 if (aColumn != draggedColumn) {
652                     paintCell(g, cellRect, column);
653                 }
654                 cellRect.x += columnWidth;
655             }
656         } else {
657             for(int column = cMax; column >= cMin; column--) {
658                 aColumn = cm.getColumn(column);
659                 columnWidth = aColumn.getWidth();
660                 cellRect.width = columnWidth;
661                 if (aColumn != draggedColumn) {
662                     paintCell(g, cellRect, column);
663                 }
664                 cellRect.x += columnWidth;
665             }
666         }
667 
668         // Paint the dragged column if we are dragging.
669         if (draggedColumn != null) {
670             int draggedColumnIndex = viewIndexForColumn(draggedColumn);
671             Rectangle draggedCellRect = header.getHeaderRect(draggedColumnIndex);
672 
673             // Draw a gray well in place of the moving column.
674             g.setColor(header.getParent().getBackground());
675             g.fillRect(draggedCellRect.x, draggedCellRect.y,
676                                draggedCellRect.width, draggedCellRect.height);
677 
678             draggedCellRect.x += header.getDraggedDistance();
679 
680             // Fill the background.
681             g.setColor(header.getBackground());
682             g.fillRect(draggedCellRect.x, draggedCellRect.y,
683                        draggedCellRect.width, draggedCellRect.height);
684 
685             paintCell(g, draggedCellRect, draggedColumnIndex);
686         }
687 
688         // Remove all components in the rendererPane.
689         rendererPane.removeAll();
690     }
691 
getHeaderRenderer(int columnIndex)692     private Component getHeaderRenderer(int columnIndex) {
693         TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
694         TableCellRenderer renderer = aColumn.getHeaderRenderer();
695         if (renderer == null) {
696             renderer = header.getDefaultRenderer();
697         }
698 
699         boolean hasFocus = !header.isPaintingForPrint()
700                            && (columnIndex == getSelectedColumnIndex())
701                            && header.hasFocus();
702         return renderer.getTableCellRendererComponent(header.getTable(),
703                                                 aColumn.getHeaderValue(),
704                                                 false, hasFocus,
705                                                 -1, columnIndex);
706     }
707 
paintCell(Graphics g, Rectangle cellRect, int columnIndex)708     private void paintCell(Graphics g, Rectangle cellRect, int columnIndex) {
709         Component component = getHeaderRenderer(columnIndex);
710         rendererPane.paintComponent(g, component, header, cellRect.x, cellRect.y,
711                             cellRect.width, cellRect.height, true);
712     }
713 
viewIndexForColumn(TableColumn aColumn)714     private int viewIndexForColumn(TableColumn aColumn) {
715         TableColumnModel cm = header.getColumnModel();
716         for (int column = 0; column < cm.getColumnCount(); column++) {
717             if (cm.getColumn(column) == aColumn) {
718                 return column;
719             }
720         }
721         return -1;
722     }
723 
724 //
725 // Size Methods
726 //
727 
getHeaderHeight()728     private int getHeaderHeight() {
729         int height = 0;
730         boolean accomodatedDefault = false;
731         TableColumnModel columnModel = header.getColumnModel();
732         for(int column = 0; column < columnModel.getColumnCount(); column++) {
733             TableColumn aColumn = columnModel.getColumn(column);
734             boolean isDefault = (aColumn.getHeaderRenderer() == null);
735 
736             if (!isDefault || !accomodatedDefault) {
737                 Component comp = getHeaderRenderer(column);
738                 int rendererHeight = comp.getPreferredSize().height;
739                 height = Math.max(height, rendererHeight);
740 
741                 // Configuring the header renderer to calculate its preferred size
742                 // is expensive. Optimise this by assuming the default renderer
743                 // always has the same height as the first non-zero height that
744                 // it returns for a non-null/non-empty value.
745                 if (isDefault && rendererHeight > 0) {
746                     Object headerValue = aColumn.getHeaderValue();
747                     if (headerValue != null) {
748                         headerValue = headerValue.toString();
749 
750                         if (headerValue != null && !headerValue.equals("")) {
751                             accomodatedDefault = true;
752                         }
753                     }
754                 }
755             }
756         }
757         return height;
758     }
759 
createHeaderSize(long width)760     private Dimension createHeaderSize(long width) {
761         // None of the callers include the intercell spacing, do it here.
762         if (width > Integer.MAX_VALUE) {
763             width = Integer.MAX_VALUE;
764         }
765         return new Dimension((int)width, getHeaderHeight());
766     }
767 
768 
769     /**
770      * Return the minimum size of the header. The minimum width is the sum
771      * of the minimum widths of each column (plus inter-cell spacing).
772      */
getMinimumSize(JComponent c)773     public Dimension getMinimumSize(JComponent c) {
774         long width = 0;
775         Enumeration enumeration = header.getColumnModel().getColumns();
776         while (enumeration.hasMoreElements()) {
777             TableColumn aColumn = (TableColumn)enumeration.nextElement();
778             width = width + aColumn.getMinWidth();
779         }
780         return createHeaderSize(width);
781     }
782 
783     /**
784      * Return the preferred size of the header. The preferred height is the
785      * maximum of the preferred heights of all of the components provided
786      * by the header renderers. The preferred width is the sum of the
787      * preferred widths of each column (plus inter-cell spacing).
788      */
getPreferredSize(JComponent c)789     public Dimension getPreferredSize(JComponent c) {
790         long width = 0;
791         Enumeration enumeration = header.getColumnModel().getColumns();
792         while (enumeration.hasMoreElements()) {
793             TableColumn aColumn = (TableColumn)enumeration.nextElement();
794             width = width + aColumn.getPreferredWidth();
795         }
796         return createHeaderSize(width);
797     }
798 
799     /**
800      * Return the maximum size of the header. The maximum width is the sum
801      * of the maximum widths of each column (plus inter-cell spacing).
802      */
getMaximumSize(JComponent c)803     public Dimension getMaximumSize(JComponent c) {
804         long width = 0;
805         Enumeration enumeration = header.getColumnModel().getColumns();
806         while (enumeration.hasMoreElements()) {
807             TableColumn aColumn = (TableColumn)enumeration.nextElement();
808             width = width + aColumn.getMaxWidth();
809         }
810         return createHeaderSize(width);
811     }
812 
813     private static class Actions extends UIAction {
814         public static final String TOGGLE_SORT_ORDER =
815             "toggleSortOrder";
816         public static final String SELECT_COLUMN_TO_LEFT =
817             "selectColumnToLeft";
818         public static final String SELECT_COLUMN_TO_RIGHT =
819             "selectColumnToRight";
820         public static final String MOVE_COLUMN_LEFT =
821             "moveColumnLeft";
822         public static final String MOVE_COLUMN_RIGHT =
823             "moveColumnRight";
824         public static final String RESIZE_LEFT =
825             "resizeLeft";
826         public static final String RESIZE_RIGHT =
827             "resizeRight";
828         public static final String FOCUS_TABLE =
829             "focusTable";
830 
Actions(String name)831         public Actions(String name) {
832             super(name);
833         }
834 
isEnabled(Object sender)835         public boolean isEnabled(Object sender) {
836             if (sender instanceof JTableHeader) {
837                 JTableHeader th = (JTableHeader)sender;
838                 TableColumnModel cm = th.getColumnModel();
839                 if (cm.getColumnCount() <= 0) {
840                     return false;
841                 }
842 
843                 String key = getName();
844                 BasicTableHeaderUI ui =
845                     (BasicTableHeaderUI)BasicLookAndFeel.getUIOfType(th.getUI(),
846                                                       BasicTableHeaderUI.class);
847                 if (ui != null) {
848                     if (key == MOVE_COLUMN_LEFT) {
849                         return th.getReorderingAllowed()
850                             && maybeMoveColumn(true, th, ui, false);
851                     } else if (key == MOVE_COLUMN_RIGHT) {
852                         return th.getReorderingAllowed()
853                             && maybeMoveColumn(false, th, ui, false);
854                     } else if (key == RESIZE_LEFT ||
855                                key == RESIZE_RIGHT) {
856                         return canResize(cm.getColumn(ui.getSelectedColumnIndex()), th);
857                     } else if (key == FOCUS_TABLE) {
858                         return (th.getTable() != null);
859                     }
860                 }
861             }
862             return true;
863         }
864 
actionPerformed(ActionEvent e)865         public void actionPerformed(ActionEvent e) {
866             JTableHeader th = (JTableHeader)e.getSource();
867             BasicTableHeaderUI ui =
868                 (BasicTableHeaderUI)BasicLookAndFeel.
869                                         getUIOfType(th.getUI(),
870                                             BasicTableHeaderUI.class);
871             if (ui == null) {
872                 return;
873             }
874 
875             String name = getName();
876             if (TOGGLE_SORT_ORDER == name) {
877                 JTable table = th.getTable();
878                 RowSorter sorter = table == null ? null : table.getRowSorter();
879                 if (sorter != null) {
880                     int columnIndex = ui.getSelectedColumnIndex();
881                     columnIndex = table.convertColumnIndexToModel(
882                                                       columnIndex);
883                     sorter.toggleSortOrder(columnIndex);
884                 }
885             } else if (SELECT_COLUMN_TO_LEFT == name) {
886                 if (th.getComponentOrientation().isLeftToRight()) {
887                     ui.selectPreviousColumn(true);
888                 } else {
889                     ui.selectNextColumn(true);
890                 }
891             } else if (SELECT_COLUMN_TO_RIGHT == name) {
892                 if (th.getComponentOrientation().isLeftToRight()) {
893                     ui.selectNextColumn(true);
894                 } else {
895                     ui.selectPreviousColumn(true);
896                 }
897             } else if (MOVE_COLUMN_LEFT == name) {
898                 moveColumn(true, th, ui);
899             } else if (MOVE_COLUMN_RIGHT == name) {
900                 moveColumn(false, th, ui);
901             } else if (RESIZE_LEFT == name) {
902                 resize(true, th, ui);
903             } else if (RESIZE_RIGHT == name) {
904                 resize(false, th, ui);
905             } else if (FOCUS_TABLE == name) {
906                 JTable table = th.getTable();
907                 if (table != null) {
908                     table.requestFocusInWindow();
909                 }
910             }
911         }
912 
moveColumn(boolean leftArrow, JTableHeader th, BasicTableHeaderUI ui)913         private void moveColumn(boolean leftArrow, JTableHeader th,
914                                 BasicTableHeaderUI ui) {
915             maybeMoveColumn(leftArrow, th, ui, true);
916         }
917 
maybeMoveColumn(boolean leftArrow, JTableHeader th, BasicTableHeaderUI ui, boolean doIt)918         private boolean maybeMoveColumn(boolean leftArrow, JTableHeader th,
919                                         BasicTableHeaderUI ui, boolean doIt) {
920             int oldIndex = ui.getSelectedColumnIndex();
921             int newIndex;
922 
923             if (th.getComponentOrientation().isLeftToRight()) {
924                 newIndex = leftArrow ? ui.selectPreviousColumn(doIt)
925                                      : ui.selectNextColumn(doIt);
926             } else {
927                 newIndex = leftArrow ? ui.selectNextColumn(doIt)
928                                      : ui.selectPreviousColumn(doIt);
929             }
930 
931             if (newIndex != oldIndex) {
932                 if (doIt) {
933                     th.getColumnModel().moveColumn(oldIndex, newIndex);
934                 } else {
935                     return true; // we'd do the move if asked
936                 }
937             }
938 
939             return false;
940         }
941 
resize(boolean leftArrow, JTableHeader th, BasicTableHeaderUI ui)942         private void resize(boolean leftArrow, JTableHeader th,
943                             BasicTableHeaderUI ui) {
944             int columnIndex = ui.getSelectedColumnIndex();
945             TableColumn resizingColumn =
946                 th.getColumnModel().getColumn(columnIndex);
947 
948             th.setResizingColumn(resizingColumn);
949             int oldWidth = resizingColumn.getWidth();
950             int newWidth = oldWidth;
951 
952             if (th.getComponentOrientation().isLeftToRight()) {
953                 newWidth = newWidth + (leftArrow ? -1 : 1);
954             } else {
955                 newWidth = newWidth + (leftArrow ? 1 : -1);
956             }
957 
958             ui.changeColumnWidth(resizingColumn, th, oldWidth, newWidth);
959         }
960     }
961 }  // End of Class BasicTableHeaderUI
962