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 
  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 
 118         private TableColumn getResizingColumn(Point p) {
 119             return getResizingColumn(p, header.columnAtPoint(p));
 120         }
 121 
 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 
 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 
 183         private void swapCursor() {
 184             Cursor tmp = header.getCursor();
 185             header.setCursor(otherCursor);
 186             otherCursor = tmp;
 187         }
 188 
 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 
 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 
 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 
 271         public void mouseEntered(MouseEvent e) {
 272             if (!header.isEnabled()) {
 273                 return;
 274             }
 275             updateRolloverColumn(e);
 276         }
 277 
 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 
 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      */
 305     protected MouseInputListener createMouseInputListener() {
 306         return new MouseInputHandler();
 307     }
 308 
 309 //
 310 //  The installation/uninstall procedures and support
 311 //
 312 
 313     public static ComponentUI createUI(JComponent h) {
 314         return new BasicTableHeaderUI();
 315     }
 316 
 317 //  Installation
 318 
 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      */
 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      */
 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      */
 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 
 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 
 379     protected void uninstallDefaults() {}
 380 
 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      */
 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      */
 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      */
 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      */
 440     protected void rolloverColumnUpdated(int oldColumn, int newColumn) {
 441     }
 442 
 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 //
 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 
 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      */
 485     void selectColumn(int newColIndex) {
 486         selectColumn(newColIndex, true);
 487     }
 488 
 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      */
 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 
 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 
 532     private static boolean canResize(TableColumn column,
 533                                      JTableHeader header) {
 534         return (column != null) && header.getResizingAllowed()
 535                                 && column.getResizable();
 536     }
 537 
 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      */
 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 
 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 
 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 
 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 
 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 
 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 
 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      */
 773     public Dimension getMinimumSize(JComponent c) {
 774         long width = 0;
 775         Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns();
 776         while (enumeration.hasMoreElements()) {
 777             TableColumn aColumn = 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      */
 789     public Dimension getPreferredSize(JComponent c) {
 790         long width = 0;
 791         Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns();
 792         while (enumeration.hasMoreElements()) {
 793             TableColumn aColumn = 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      */
 803     public Dimension getMaximumSize(JComponent c) {
 804         long width = 0;
 805         Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns();
 806         while (enumeration.hasMoreElements()) {
 807             TableColumn aColumn = 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 
 831         public Actions(String name) {
 832             super(name);
 833         }
 834 
 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 
 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 
 913         private void moveColumn(boolean leftArrow, JTableHeader th,
 914                                 BasicTableHeaderUI ui) {
 915             maybeMoveColumn(leftArrow, th, ui, true);
 916         }
 917 
 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 
 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