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:       // Nothing to do.
 203:     }
 204: 
 205:     /**
 206:      * Change the mouse cursor if the mouse if above the column boundary.
 207:      */
 208:     public void mouseMoved(MouseEvent e)
 209:     {
 210:       // When dragging, the functionality is handled by the mouseDragged.
 211:       if (e.getButton() == 0 && header.getResizingAllowed())
 212:         {
 213:           TableColumnModel model = header.getColumnModel();
 214:           int n = model.getColumnCount();
 215:           if (n < 2)
 216:             // It must be at least two columns to have at least one boundary.
 217:             // Otherwise, nothing to do.
 218:             return;
 219: 
 220:           boolean onBoundary = false;
 221: 
 222:           int x = e.getX();
 223:           int a = x - COLUMN_BOUNDARY_TOLERANCE;
 224:           int b = x + COLUMN_BOUNDARY_TOLERANCE;
 225: 
 226:           int p = 0;
 227: 
 228:           Scan: for (int i = 0; i < n - 1; i++)
 229:             {
 230:               p += model.getColumn(i).getWidth();
 231: 
 232:               if (p >= a && p <= b)
 233:                 {
 234:                   TableColumn column = model.getColumn(i);
 235:                   onBoundary = true;
 236: 
 237:                   draggingFrom = x;
 238:                   prevPrefWidth = column.getWidth();
 239:                   header.setResizingColumn(column);
 240:                   break Scan;
 241:                 }
 242:             }
 243: 
 244:           if (onBoundary != showingResizeCursor)
 245:             {
 246:               // Change the cursor shape, if needed.
 247:               if (onBoundary)
 248:                 {
 249: 
 250:           originalCursor = header.getCursor();
 251:                   if (p < x)
 252:                     header.setCursor(Cursor.getPredefinedCursor(
 253:                         Cursor.W_RESIZE_CURSOR));
 254:                   else
 255:                     header.setCursor(Cursor.getPredefinedCursor(
 256:                         Cursor.E_RESIZE_CURSOR));
 257:                 }
 258:               else
 259:                 {
 260:                   header.setCursor(originalCursor);
 261:                   header.setResizingColumn(null);
 262:                 }
 263: 
 264:               showingResizeCursor = onBoundary;
 265:             }
 266:         }
 267:     }
 268: 
 269:     /**
 270:      * Starts the dragging/resizing procedure.
 271:      */
 272:     public void mousePressed(MouseEvent e)
 273:     {
 274:       if (header.getResizingAllowed())
 275:         {
 276:           TableColumn resizingColumn = header.getResizingColumn();
 277:           if (resizingColumn != null)
 278:             {
 279:               resizingColumn.setPreferredWidth(resizingColumn.getWidth());
 280:               return;
 281:             }
 282:         }
 283: 
 284:       if (header.getReorderingAllowed())
 285:         {
 286:           TableColumnModel model = header.getColumnModel();
 287:           int n = model.getColumnCount();
 288:           if (n < 2)
 289:             // It must be at least two columns to change the column location.
 290:             return;
 291: 
 292:           boolean onBoundary = false;
 293: 
 294:           int x = e.getX();
 295:           int p = 0;
 296:           int col = - 1;
 297: 
 298:           Scan: for (int i = 0; i < n; i++)
 299:             {
 300:               p += model.getColumn(i).getWidth();
 301:               if (p > x)
 302:                 {
 303:                   col = i;
 304:                   break Scan;
 305:                 }
 306:             }
 307:           if (col < 0)
 308:             return;
 309: 
 310:           TableColumn dragIt = model.getColumn(col);
 311:           header.setDraggedColumn(dragIt);
 312: 
 313:           draggingFrom = (p - dragIt.getWidth()) - x;
 314:           draggingHeaderRect = new Rectangle(header.getHeaderRect(col));
 315:           draggingColumnNumber = col;
 316:         }
 317:     }
 318: 
 319:     /**
 320:      * Set all column preferred width to the current width to prevend abrupt
 321:      * width changes during the next resize.
 322:      */
 323:     public void mouseReleased(MouseEvent e)
 324:     {
 325:       if (header.getResizingColumn() != null && header.getResizingAllowed())
 326:         endResizing();
 327:       if (header.getDraggedColumn() != null &&  header.getReorderingAllowed())
 328:         endDragging(e);
 329:     }
 330: 
 331:     /**
 332:      * Stop resizing session.
 333:      */
 334:     void endResizing()
 335:     {
 336:       TableColumnModel model = header.getColumnModel();
 337:       int n = model.getColumnCount();
 338:       if (n > 2)
 339:         {
 340:           TableColumn c;
 341:           for (int i = 0; i < n; i++)
 342:             {
 343:               c = model.getColumn(i);
 344:               c.setPreferredWidth(c.getWidth());
 345:             }
 346:         }
 347:       header.setResizingColumn(null);
 348:       showingResizeCursor = false;
 349:       if (timer != null)
 350:         timer.stop();
 351:       header.setCursor(originalCursor);
 352:     }
 353: 
 354:     /**
 355:      * Stop the dragging session.
 356:      * 
 357:      * @param e the "mouse release" mouse event, needed to determing the final
 358:      *          location for the dragged column.
 359:      */
 360:     void endDragging(MouseEvent e)
 361:     {
 362:       header.setDraggedColumn(null);
 363:       draggingHeaderRect = null;
 364: 
 365:       TableColumnModel model = header.getColumnModel();
 366: 
 367:       // Find where have we dragged the column.
 368:       int x = e.getX();
 369:       int p = 0;
 370:       
 371:       int col = model.getColumnCount() - 1;
 372:       int n = model.getColumnCount();
 373: 
 374:       // This loop does not find the column if the mouse if out of the 
 375:       // right boundary of the table header. Then we make this column the
 376:       // rightmost column.
 377:       Scan: for (int i = 0; i < n; i++)
 378:         {
 379:           p += model.getColumn(i).getWidth();
 380:           if (p > x)
 381:             {
 382:               col = i;
 383:               break Scan;
 384:             }
 385:         }
 386: 
 387:       header.getTable().moveColumn(draggingColumnNumber, col);
 388:     }
 389:   }
 390:  
 391:   /**
 392:    * Create and return the mouse input listener.
 393:    * 
 394:    * @return the mouse listener ({@link MouseInputHandler}, if not overridden.
 395:    */
 396:   protected MouseInputListener createMouseInputListener()
 397:   {
 398:     return new MouseInputHandler();
 399:   }
 400:   
 401:   /**
 402:    * Construct a new BasicTableHeaderUI, create mouse listeners.
 403:    */
 404:   public BasicTableHeaderUI()
 405:   {
 406:     mouseInputListener = createMouseInputListener();
 407:   }
 408: 
 409:   protected void installDefaults()
 410:   {
 411:     LookAndFeel.installColorsAndFont(header, "TableHeader.background",
 412:                                      "TableHeader.foreground",
 413:                                      "TableHeader.font");
 414:     cellBorder = UIManager.getBorder("TableHeader.cellBorder");
 415:   }
 416: 
 417:   protected void installKeyboardActions()
 418:     throws NotImplementedException
 419:   {
 420:     // TODO: Implement this properly.
 421:   }
 422: 
 423:   /**
 424:    * Add the mouse listener and the mouse motion listener to the table
 425:    * header. The listeners support table column resizing and rearrangement
 426:    * by mouse.
 427:    */
 428:   protected void installListeners()
 429:   {
 430:     header.addMouseListener(mouseInputListener);
 431:     header.addMouseMotionListener(mouseInputListener);
 432:   }
 433: 
 434:   public void installUI(JComponent c)
 435:   {
 436:     header = (JTableHeader) c;
 437:     rendererPane = new CellRendererPane();
 438:     installDefaults();
 439:     installKeyboardActions();
 440:     installListeners();
 441:   }
 442: 
 443:   protected void uninstallDefaults()
 444:   {
 445:     header.setBackground(null);
 446:     header.setForeground(null);
 447:     header.setFont(null);
 448:   }
 449: 
 450:   protected void uninstallKeyboardActions()
 451:     throws NotImplementedException
 452:   {
 453:     // TODO: Implement this properly.
 454:   }
 455:   
 456:   /**
 457:    * Remove the previously installed listeners.
 458:    */
 459:   protected void uninstallListeners()
 460:   {
 461:     header.removeMouseListener(mouseInputListener);
 462:     header.removeMouseMotionListener(mouseInputListener);
 463:   }
 464: 
 465:   public void uninstallUI(JComponent c)
 466:   {
 467:     uninstallListeners();
 468:     uninstallKeyboardActions();
 469:     uninstallDefaults();
 470:   }
 471:   
 472:   /**
 473:    * Repaint the table header. 
 474:    */
 475:   public void paint(Graphics gfx, JComponent c)
 476:   {
 477:     TableColumnModel cmod = header.getColumnModel();
 478:     int ncols = cmod.getColumnCount();
 479:     if (ncols == 0)
 480:       return;
 481:     
 482:     Rectangle clip = gfx.getClipBounds();
 483:     TableCellRenderer defaultRend = header.getDefaultRenderer();
 484: 
 485:     for (int i = 0; i < ncols; ++i)
 486:       {
 487:         Rectangle bounds = header.getHeaderRect(i);
 488:         if (bounds.intersects(clip))
 489:           {
 490:             Rectangle oldClip = gfx.getClipBounds();
 491:             TableColumn col = cmod.getColumn(i);
 492:             TableCellRenderer rend = col.getHeaderRenderer();
 493:             if (rend == null)
 494:               rend = defaultRend;
 495:             Object val = col.getHeaderValue();
 496:             Component comp = rend.getTableCellRendererComponent(header.getTable(),
 497:                                                                 val,
 498:                                                                 false, // isSelected
 499:                                                                 false, // isFocused
 500:                                                                 -1, i);
 501:             // FIXME: The following settings should be performed in
 502:             // rend.getTableCellRendererComponent().
 503:             comp.setFont(header.getFont());
 504:             comp.setBackground(header.getBackground());
 505:             comp.setForeground(header.getForeground());
 506:             if (comp instanceof JComponent)
 507:               ((JComponent) comp).setBorder(cellBorder);
 508:             rendererPane.paintComponent(gfx, comp, header, bounds.x, bounds.y,
 509:                                         bounds.width, bounds.height);
 510:           }
 511:       }
 512:     
 513:     // This displays a running rectangle that is much simplier than the total
 514:     // animation, as it is seen in Sun's application.
 515:     // TODO animate the collumn dragging like in Sun's jre.
 516:     if (draggingHeaderRect != null)
 517:       {
 518:         gfx.setColor(header.getForeground()); 
 519:         gfx.drawRect(draggingHeaderRect.x, draggingHeaderRect.y + 2,
 520:             draggingHeaderRect.width - 1, draggingHeaderRect.height - 6);
 521:       }
 522:   }
 523:   
 524:   /**
 525:    * Get the preferred header size.
 526:    * 
 527:    * @param ignored unused
 528:    * 
 529:    * @return the preferred size of the associated header.
 530:    */
 531:   public Dimension getPreferredSize(JComponent ignored)
 532:   {
 533:     TableColumnModel cmod = header.getColumnModel();
 534:     TableCellRenderer defaultRend = header.getDefaultRenderer();
 535:     int ncols = cmod.getColumnCount();    
 536:     Dimension ret = new Dimension(0, 0);
 537:     int spacing = 0;
 538: 
 539:     if (header.getTable() != null 
 540:         && header.getTable().getIntercellSpacing() != null)
 541:       spacing = header.getTable().getIntercellSpacing().width;
 542:     
 543:     for (int i = 0; i < ncols; ++i)      
 544:       {
 545:         TableColumn col = cmod.getColumn(i);
 546:         TableCellRenderer rend = col.getHeaderRenderer();
 547:         if (rend == null)
 548:           rend = defaultRend;
 549:         Object val = col.getHeaderValue();
 550:         Component comp = rend.getTableCellRendererComponent(header.getTable(),
 551:                                                             val,
 552:                                                             false, // isSelected
 553:                                                             false, // isFocused
 554:                                                             -1, i);
 555:         comp.setFont(header.getFont());
 556:         comp.setBackground(header.getBackground());
 557:         comp.setForeground(header.getForeground());
 558:         if (comp instanceof JComponent)
 559:           ((JComponent) comp).setBorder(cellBorder);
 560: 
 561:         Dimension d = comp.getPreferredSize();
 562:         ret.width += spacing;
 563:         ret.height = Math.max(d.height, ret.height);        
 564:       }
 565:     ret.width = cmod.getTotalColumnWidth();
 566:     return ret;
 567:   }
 568:   
 569:   
 570: }