001/* BasicTableHeaderUI.java -- 002 Copyright (C) 2004 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.plaf.basic; 040 041import java.awt.Component; 042import java.awt.Cursor; 043import java.awt.Dimension; 044import java.awt.Graphics; 045import java.awt.Rectangle; 046import java.awt.event.ActionEvent; 047import java.awt.event.ActionListener; 048import java.awt.event.MouseEvent; 049 050import javax.swing.CellRendererPane; 051import javax.swing.JComponent; 052import javax.swing.LookAndFeel; 053import javax.swing.Timer; 054import javax.swing.UIManager; 055import javax.swing.border.Border; 056import javax.swing.event.MouseInputListener; 057import javax.swing.plaf.ComponentUI; 058import javax.swing.plaf.TableHeaderUI; 059import javax.swing.table.JTableHeader; 060import javax.swing.table.TableCellRenderer; 061import javax.swing.table.TableColumn; 062import javax.swing.table.TableColumnModel; 063 064/** 065 * Basic pluggable look and feel interface for JTableHeader. 066 */ 067public class BasicTableHeaderUI extends TableHeaderUI 068{ 069 /** 070 * The width of the space (in both direction) around the column boundary, 071 * where mouse cursor changes shape into "resize" 072 */ 073 static int COLUMN_BOUNDARY_TOLERANCE = 3; 074 075 public static ComponentUI createUI(JComponent h) 076 { 077 return new BasicTableHeaderUI(); 078 } 079 080 /** 081 * The table header that is using this interface. 082 */ 083 protected JTableHeader header; 084 085 /** 086 * The mouse input listener, responsible for mouse manipulations with 087 * the table header. 088 */ 089 protected MouseInputListener mouseInputListener; 090 091 /** 092 * Paint the header cell. 093 */ 094 protected CellRendererPane rendererPane; 095 096 /** 097 * The header cell border. 098 */ 099 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}