OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / plaf / basic / BasicTableHeaderUI.java
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 }