001 /* BasicTableHeaderUI.java --
002 Copyright (C) 2004 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.plaf.basic;
040
041 import java.awt.Component;
042 import java.awt.Cursor;
043 import java.awt.Dimension;
044 import java.awt.Graphics;
045 import java.awt.Rectangle;
046 import java.awt.event.ActionEvent;
047 import java.awt.event.ActionListener;
048 import java.awt.event.MouseEvent;
049
050 import javax.swing.CellRendererPane;
051 import javax.swing.JComponent;
052 import javax.swing.LookAndFeel;
053 import javax.swing.Timer;
054 import javax.swing.UIManager;
055 import javax.swing.border.Border;
056 import javax.swing.event.MouseInputListener;
057 import javax.swing.plaf.ComponentUI;
058 import javax.swing.plaf.TableHeaderUI;
059 import javax.swing.table.JTableHeader;
060 import javax.swing.table.TableCellRenderer;
061 import javax.swing.table.TableColumn;
062 import javax.swing.table.TableColumnModel;
063
064 /**
065 * Basic pluggable look and feel interface for JTableHeader.
066 */
067 public 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 }