Source for javax.swing.plaf.basic.BasicTableHeaderUI

   1: /* BasicTableHeaderUI.java --
   2:    Copyright (C) 2004 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.plaf.basic;
  40: 
  41: import java.awt.Component;
  42: import java.awt.Cursor;
  43: import java.awt.Dimension;
  44: import java.awt.Graphics;
  45: import java.awt.Rectangle;
  46: import java.awt.event.ActionEvent;
  47: import java.awt.event.ActionListener;
  48: import java.awt.event.MouseEvent;
  49: 
  50: import javax.swing.CellRendererPane;
  51: import javax.swing.JComponent;
  52: import javax.swing.LookAndFeel;
  53: import javax.swing.Timer;
  54: import javax.swing.UIManager;
  55: import javax.swing.border.Border;
  56: import javax.swing.event.MouseInputListener;
  57: import javax.swing.plaf.ComponentUI;
  58: import javax.swing.plaf.TableHeaderUI;
  59: import javax.swing.table.JTableHeader;
  60: import javax.swing.table.TableCellRenderer;
  61: import javax.swing.table.TableColumn;
  62: import javax.swing.table.TableColumnModel;
  63: 
  64: /**
  65:  * Basic pluggable look and feel interface for JTableHeader.
  66:  */
  67: public class BasicTableHeaderUI extends TableHeaderUI
  68: {
  69:   /**
  70:    * The width of the space (in both direction) around the column boundary,
  71:    * where mouse cursor changes shape into "resize"
  72:    */
  73:   static int COLUMN_BOUNDARY_TOLERANCE = 3;
  74:   
  75:   public static ComponentUI createUI(JComponent h)
  76:   {
  77:     return new BasicTableHeaderUI();
  78:   }
  79:   
  80:   /**
  81:    * The table header that is using this interface.
  82:    */
  83:   protected JTableHeader header;
  84:   
  85:   /**
  86:    * The mouse input listener, responsible for mouse manipulations with
  87:    * the table header.
  88:    */
  89:   protected MouseInputListener mouseInputListener;
  90:   
  91:   /**
  92:    * Paint the header cell.
  93:    */
  94:   protected CellRendererPane rendererPane;
  95:   
  96:   /**
  97:    * The header cell border.
  98:    */
  99:   private Border cellBorder;
 100: 
 101:   /**
 102:    * Original mouse cursor prior to resizing.
 103:    */
 104:   private Cursor originalCursor;
 105:   
 106:   /**
 107:    * If not null, one of the columns is currently being dragged.
 108:    */
 109:   Rectangle draggingHeaderRect;
 110:   
 111:   /**
 112:    * Handles column movement and rearrangement by mouse. The same instance works
 113:    * both as mouse listener and the mouse motion listner.
 114:    */
 115:   public class MouseInputHandler
 116:       implements MouseInputListener
 117:   {
 118:     /**
 119:      * If true, the cursor is being already shown in the alternative "resize"
 120:      * shape. 
 121:      */
 122:     boolean showingResizeCursor;
 123: 
 124:     /**
 125:      * The position, from where the cursor is dragged during resizing. Double
 126:      * purpose field (absolute value during resizing and relative offset during
 127:      * column dragging).
 128:      */
 129:     int draggingFrom = - 1;
 130:     
 131:     /**
 132:      * The number of the column being dragged.
 133:      */
 134:     int draggingColumnNumber;    
 135: 
 136:     /**
 137:      * The previous preferred width of the column.
 138:      */
 139:     int prevPrefWidth = - 1;
 140: 
 141:     /**
 142:      * The timer to coalesce column resizing events.
 143:      */
 144:     Timer timer;
 145: 
 146:     /**
 147:      * Returns without action, part of the MouseInputListener interface.
 148:      */
 149:     public void mouseClicked(MouseEvent e)
 150:     {
 151:       // Nothing to do.
 152:     }
 153: 
 154:     /**
 155:      * If being in the resizing mode, handle resizing.
 156:      */
 157:     public void mouseDragged(MouseEvent e)
 158:     {
 159:       TableColumn resizeIt = header.getResizingColumn();
 160:       if (resizeIt != null && header.getResizingAllowed())
 161:         {
 162:           // The timer is intialised on demand.
 163:           if (timer == null)
 164:             {
 165:               // The purpose of timer is to coalesce events. If the queue
 166:               // is free, the repaint event is fired immediately.
 167:               timer = new Timer(1, new ActionListener()
 168:               {
 169:                 public void actionPerformed(ActionEvent e)
 170:                 {
 171:                   header.getTable().doLayout();
 172:                 }
 173:               });
 174:               timer.setRepeats(false);
 175:               timer.setCoalesce(true);
 176:             }
 177:           resizeIt.setPreferredWidth(prevPrefWidth + e.getX() - draggingFrom);
 178:           timer.restart();
 179:         }
 180:       else if (draggingHeaderRect != null && header.getReorderingAllowed())
 181:         {
 182:           draggingHeaderRect.x = e.getX() + draggingFrom;
 183:           header.repaint();
 184:         }
 185:     }
 186: 
 187:     /**
 188:      * Returns without action, part of the MouseInputListener interface.
 189:      */
 190:     public void mouseEntered(MouseEvent e)
 191:     {
 192:       // Nothing to do.
 193:     }
 194: 
 195:     /**
 196:      * Reset drag information of the column resizing.
 197:      */
 198:     public void mouseExited(MouseEvent e)
 199:     {
 200:       // Nothing to do.
 201:     }
 202: 
 203:     /**
 204:      * Change the mouse cursor if the mouse if above the column boundary.
 205:      */
 206:     public void mouseMoved(MouseEvent e)
 207:     {
 208:       // When dragging, the functionality is handled by the mouseDragged.
 209:       if (e.getButton() == 0 && header.getResizingAllowed())
 210:         {
 211:           TableColumnModel model = header.getColumnModel();
 212:           int n = model.getColumnCount();
 213:           if (n < 2)
 214:             // It must be at least two columns to have at least one boundary.
 215:             // Otherwise, nothing to do.
 216:             return;
 217: 
 218:           boolean onBoundary = false;
 219: 
 220:           int x = e.getX();
 221:           int a = x - COLUMN_BOUNDARY_TOLERANCE;
 222:           int b = x + COLUMN_BOUNDARY_TOLERANCE;
 223: 
 224:           int p = 0;
 225: 
 226:           Scan: for (int i = 0; i < n - 1; i++)
 227:             {
 228:               p += model.getColumn(i).getWidth();
 229: 
 230:               if (p >= a && p <= b)
 231:                 {
 232:                   TableColumn column = model.getColumn(i);
 233:                   onBoundary = true;
 234: 
 235:                   draggingFrom = x;
 236:                   prevPrefWidth = column.getWidth();
 237:                   header.setResizingColumn(column);
 238:                   break Scan;
 239:                 }
 240:             }
 241: 
 242:           if (onBoundary != showingResizeCursor)
 243:             {
 244:               // Change the cursor shape, if needed.
 245:               if (onBoundary)
 246:                 {
 247: 
 248:           originalCursor = header.getCursor();
 249:                   if (p < x)
 250:                     header.setCursor(Cursor.getPredefinedCursor(
 251:                         Cursor.W_RESIZE_CURSOR));
 252:                   else
 253:                     header.setCursor(Cursor.getPredefinedCursor(
 254:                         Cursor.E_RESIZE_CURSOR));
 255:                 }
 256:               else
 257:                 {
 258:                   header.setCursor(originalCursor);
 259:                   header.setResizingColumn(null);
 260:                 }
 261: 
 262:               showingResizeCursor = onBoundary;
 263:             }
 264:         }
 265:     }
 266: 
 267:     /**
 268:      * Starts the dragging/resizing procedure.
 269:      */
 270:     public void mousePressed(MouseEvent e)
 271:     {
 272:       if (header.getResizingAllowed())
 273:         {
 274:           TableColumn resizingColumn = header.getResizingColumn();
 275:           if (resizingColumn != null)
 276:             {
 277:               resizingColumn.setPreferredWidth(resizingColumn.getWidth());
 278:               return;
 279:             }
 280:         }
 281: 
 282:       if (header.getReorderingAllowed())
 283:         {
 284:           TableColumnModel model = header.getColumnModel();
 285:           int n = model.getColumnCount();
 286:           if (n < 2)
 287:             // It must be at least two columns to change the column location.
 288:             return;
 289: 
 290:           boolean onBoundary = false;
 291: 
 292:           int x = e.getX();
 293:           int p = 0;
 294:           int col = - 1;
 295: 
 296:           Scan: for (int i = 0; i < n; i++)
 297:             {
 298:               p += model.getColumn(i).getWidth();
 299:               if (p > x)
 300:                 {
 301:                   col = i;
 302:                   break Scan;
 303:                 }
 304:             }
 305:           if (col < 0)
 306:             return;
 307: 
 308:           TableColumn dragIt = model.getColumn(col);
 309:           header.setDraggedColumn(dragIt);
 310: 
 311:           draggingFrom = (p - dragIt.getWidth()) - x;
 312:           draggingHeaderRect = new Rectangle(header.getHeaderRect(col));
 313:           draggingColumnNumber = col;
 314:         }
 315:     }
 316: 
 317:     /**
 318:      * Set all column preferred width to the current width to prevend abrupt
 319:      * width changes during the next resize.
 320:      */
 321:     public void mouseReleased(MouseEvent e)
 322:     {
 323:       if (header.getResizingColumn() != null && header.getResizingAllowed())
 324:         endResizing();
 325:       if (header.getDraggedColumn() != null &&  header.getReorderingAllowed())
 326:         endDragging(e);
 327:     }
 328: 
 329:     /**
 330:      * Stop resizing session.
 331:      */
 332:     void endResizing()
 333:     {
 334:       TableColumnModel model = header.getColumnModel();
 335:       int n = model.getColumnCount();
 336:       if (n > 2)
 337:         {
 338:           TableColumn c;
 339:           for (int i = 0; i < n; i++)
 340:             {
 341:               c = model.getColumn(i);
 342:               c.setPreferredWidth(c.getWidth());
 343:             }
 344:         }
 345:       header.setResizingColumn(null);
 346:       showingResizeCursor = false;
 347:       if (timer != null)
 348:         timer.stop();
 349:       header.setCursor(originalCursor);
 350:     }
 351: 
 352:     /**
 353:      * Stop the dragging session.
 354:      * 
 355:      * @param e the "mouse release" mouse event, needed to determing the final
 356:      *          location for the dragged column.
 357:      */
 358:     void endDragging(MouseEvent e)
 359:     {
 360:       header.setDraggedColumn(null);
 361:       draggingHeaderRect = null;
 362: 
 363:       TableColumnModel model = header.getColumnModel();
 364: 
 365:       // Find where have we dragged the column.
 366:       int x = e.getX();
 367:       int p = 0;
 368:       
 369:       int col = model.getColumnCount() - 1;
 370:       int n = model.getColumnCount();
 371: 
 372:       // This loop does not find the column if the mouse if out of the 
 373:       // right boundary of the table header. Then we make this column the
 374:       // rightmost column.
 375:       Scan: for (int i = 0; i < n; i++)
 376:         {
 377:           p += model.getColumn(i).getWidth();
 378:           if (p > x)
 379:             {
 380:               col = i;
 381:               break Scan;
 382:             }
 383:         }
 384: 
 385:       header.getTable().moveColumn(draggingColumnNumber, col);
 386:     }
 387:   }
 388:  
 389:   /**
 390:    * Create and return the mouse input listener.
 391:    * 
 392:    * @return the mouse listener ({@link MouseInputHandler}, if not overridden.
 393:    */
 394:   protected MouseInputListener createMouseInputListener()
 395:   {
 396:     return new MouseInputHandler();
 397:   }
 398:   
 399:   /**
 400:    * Construct a new BasicTableHeaderUI, create mouse listeners.
 401:    */
 402:   public BasicTableHeaderUI()
 403:   {
 404:     mouseInputListener = createMouseInputListener();
 405:   }
 406: 
 407:   protected void installDefaults()
 408:   {
 409:     LookAndFeel.installColorsAndFont(header, "TableHeader.background",
 410:                                      "TableHeader.foreground",
 411:                                      "TableHeader.font");
 412:     cellBorder = UIManager.getBorder("TableHeader.cellBorder");
 413:   }
 414: 
 415:   protected void installKeyboardActions()
 416:   {
 417:     // AFAICS, the RI does nothing here.
 418:   }
 419: 
 420:   /**
 421:    * Add the mouse listener and the mouse motion listener to the table
 422:    * header. The listeners support table column resizing and rearrangement
 423:    * by mouse.
 424:    */
 425:   protected void installListeners()
 426:   {
 427:     header.addMouseListener(mouseInputListener);
 428:     header.addMouseMotionListener(mouseInputListener);
 429:   }
 430: 
 431:   public void installUI(JComponent c)
 432:   {
 433:     header = (JTableHeader) c;
 434:     rendererPane = new CellRendererPane();
 435:     installDefaults();
 436:     installKeyboardActions();
 437:     installListeners();
 438:   }
 439: 
 440:   protected void uninstallDefaults()
 441:   {
 442:     header.setBackground(null);
 443:     header.setForeground(null);
 444:     header.setFont(null);
 445:   }
 446: 
 447:   protected void uninstallKeyboardActions()
 448:   {
 449:     // AFAICS, the RI does nothing here.
 450:   }
 451:   
 452:   /**
 453:    * Remove the previously installed listeners.
 454:    */
 455:   protected void uninstallListeners()
 456:   {
 457:     header.removeMouseListener(mouseInputListener);
 458:     header.removeMouseMotionListener(mouseInputListener);
 459:   }
 460: 
 461:   public void uninstallUI(JComponent c)
 462:   {
 463:     uninstallListeners();
 464:     uninstallKeyboardActions();
 465:     uninstallDefaults();
 466:   }
 467:   
 468:   /**
 469:    * Repaint the table header. 
 470:    */
 471:   public void paint(Graphics gfx, JComponent c)
 472:   {
 473:     TableColumnModel cmod = header.getColumnModel();
 474:     int ncols = cmod.getColumnCount();
 475:     if (ncols == 0)
 476:       return;
 477:     
 478:     Rectangle clip = gfx.getClipBounds();
 479:     TableCellRenderer defaultRend = header.getDefaultRenderer();
 480: 
 481:     for (int i = 0; i < ncols; ++i)
 482:       {
 483:         Rectangle bounds = header.getHeaderRect(i);
 484:         if (bounds.intersects(clip))
 485:           {
 486:             Rectangle oldClip = gfx.getClipBounds();
 487:             TableColumn col = cmod.getColumn(i);
 488:             TableCellRenderer rend = col.getHeaderRenderer();
 489:             if (rend == null)
 490:               rend = defaultRend;
 491:             Object val = col.getHeaderValue();
 492:             Component comp = rend.getTableCellRendererComponent(header.getTable(),
 493:                                                                 val,
 494:                                                                 false, // isSelected
 495:                                                                 false, // isFocused
 496:                                                                 -1, i);
 497:             // FIXME: The following settings should be performed in
 498:             // rend.getTableCellRendererComponent().
 499:             comp.setFont(header.getFont());
 500:             comp.setBackground(header.getBackground());
 501:             comp.setForeground(header.getForeground());
 502:             if (comp instanceof JComponent)
 503:               ((JComponent) comp).setBorder(cellBorder);
 504:             rendererPane.paintComponent(gfx, comp, header, bounds.x, bounds.y,
 505:                                         bounds.width, bounds.height);
 506:           }
 507:       }
 508:     
 509:     // This displays a running rectangle that is much simplier than the total
 510:     // animation, as it is seen in Sun's application.
 511:     // TODO animate the collumn dragging like in Sun's jre.
 512:     if (draggingHeaderRect != null)
 513:       {
 514:         gfx.setColor(header.getForeground()); 
 515:         gfx.drawRect(draggingHeaderRect.x, draggingHeaderRect.y + 2,
 516:             draggingHeaderRect.width - 1, draggingHeaderRect.height - 6);
 517:       }
 518:   }
 519:   
 520:   /**
 521:    * Get the preferred header size.
 522:    * 
 523:    * @param ignored unused
 524:    * 
 525:    * @return the preferred size of the associated header.
 526:    */
 527:   public Dimension getPreferredSize(JComponent ignored)
 528:   {
 529:     TableColumnModel cmod = header.getColumnModel();
 530:     TableCellRenderer defaultRend = header.getDefaultRenderer();
 531:     int ncols = cmod.getColumnCount();    
 532:     Dimension ret = new Dimension(0, 0);
 533:     int spacing = 0;
 534: 
 535:     if (header.getTable() != null 
 536:         && header.getTable().getIntercellSpacing() != null)
 537:       spacing = header.getTable().getIntercellSpacing().width;
 538:     
 539:     for (int i = 0; i < ncols; ++i)      
 540:       {
 541:         TableColumn col = cmod.getColumn(i);
 542:         TableCellRenderer rend = col.getHeaderRenderer();
 543:         if (rend == null)
 544:           rend = defaultRend;
 545:         Object val = col.getHeaderValue();
 546:         Component comp = rend.getTableCellRendererComponent(header.getTable(),
 547:                                                             val,
 548:                                                             false, // isSelected
 549:                                                             false, // isFocused
 550:                                                             -1, i);
 551:         comp.setFont(header.getFont());
 552:         comp.setBackground(header.getBackground());
 553:         comp.setForeground(header.getForeground());
 554:         if (comp instanceof JComponent)
 555:           ((JComponent) comp).setBorder(cellBorder);
 556: 
 557:         Dimension d = comp.getPreferredSize();
 558:         ret.width += spacing;
 559:         ret.height = Math.max(d.height, ret.height);        
 560:       }
 561:     ret.width = cmod.getTotalColumnWidth();
 562:     return ret;
 563:   }
 564:   
 565:   
 566: }