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