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