KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > swing > tabcontrol > plaf > AbstractTabCellRenderer


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 /*
20  * AbstractTabCellRenderer.java
21  *
22  * Created on December 2, 2003, 4:13 PM
23  */

24
25 package org.netbeans.swing.tabcontrol.plaf;
26
27 import org.netbeans.swing.tabcontrol.TabData;
28 import org.netbeans.swing.tabcontrol.TabDisplayer;
29
30 import org.openide.awt.HtmlRenderer;
31
32 import javax.swing.*;
33 import javax.swing.border.Border JavaDoc;
34 import java.awt.*;
35 import java.awt.event.ContainerListener JavaDoc;
36 import java.awt.event.HierarchyBoundsListener JavaDoc;
37 import java.awt.event.HierarchyListener JavaDoc;
38 import java.awt.event.MouseEvent JavaDoc;
39
40 /**
41  * Base class for tab renderers for the tab control. This is a support class
42  * which will allow authors who want to provide a different look or behavior for
43  * tabbed controls with a minimum of coding. The main methods of interest are
44  * <ul><li><code>stateChanged()</code> - where the component should be
45  * configured to render a given tab</li><li><code>getState()</code> - where the
46  * current state is to be found at the time stateChanged is called</li>
47  * </ul>.
48  * <p>
49  * Typical usage is to pass one or more TabPainter objects to the constructor
50  * which will be responsible for doing the actual painting, calling the convenience
51  * getters in this class (such as <code>isSelected</code>) to determine how
52  * to paint.
53  *
54  *
55  * @author Tim Boudreau
56  */

57 public abstract class AbstractTabCellRenderer extends JLabel
58         implements TabCellRenderer {
59     private int state = TabState.NOT_ONSCREEN;
60     TabPainter leftBorder;
61     TabPainter rightBorder;
62     TabPainter normalBorder;
63     private Dimension padding;
64
65     /**
66      * Creates a new instance of AbstractTabCellRenderer
67      */

68     public AbstractTabCellRenderer(TabPainter leftClip, TabPainter noClip,
69                                    TabPainter rightClip, Dimension padding) {
70         setOpaque(false);
71         setFocusable(false);
72         setBorder(noClip);
73         normalBorder = noClip;
74         leftBorder = leftClip;
75         rightBorder = rightClip;
76         this.padding = padding;
77     }
78
79     public AbstractTabCellRenderer (TabPainter painter, Dimension padding) {
80         this (painter, painter, painter, padding);
81     }
82     
83     private boolean showClose = true;
84     public final void setShowCloseButton (boolean b) {
85         showClose = b;
86     }
87     
88     public final boolean isShowCloseButton() {
89         return showClose;
90     }
91
92     private Rectangle scratch = new Rectangle();
93     public String JavaDoc getCommandAtPoint(Point p, int tabState, Rectangle bounds) {
94         setBounds (bounds);
95         setState (tabState);
96         if (supportsCloseButton(getBorder()) && isShowCloseButton()) {
97             TabPainter cbp = (TabPainter) getBorder();
98             cbp.getCloseButtonRectangle (this, scratch, bounds);
99             if (getClass() != AquaEditorTabCellRenderer.class) {
100                 //#47408 - hit test area of close button is too small
101
scratch.x -=3;
102                 scratch.y -=3;
103                 scratch.width += 6;
104                 scratch.height += 6;
105             }
106             if (scratch.contains(p)) {
107                 return TabDisplayer.COMMAND_CLOSE;
108             }
109         }
110         Polygon tabShape = getTabShape (tabState, bounds);
111         if (tabShape.contains(p)) {
112             return TabDisplayer.COMMAND_SELECT;
113         }
114         return null;
115     }
116
117     public String JavaDoc getCommandAtPoint(Point p, int tabState, Rectangle bounds, int mouseButton, int eventType, int modifiers) {
118         String JavaDoc result = null;
119         if (mouseButton == MouseEvent.BUTTON2 && eventType == MouseEvent.MOUSE_RELEASED) {
120             result = TabDisplayer.COMMAND_CLOSE;
121         }
122         else {
123             result = getCommandAtPoint (p, tabState, bounds);
124         }
125         if (result != null) {
126              if (TabDisplayer.COMMAND_SELECT == result) {
127                  boolean clipped = isClipLeft() || isClipRight();
128                  if ((clipped && eventType == MouseEvent.MOUSE_RELEASED && mouseButton == MouseEvent.BUTTON1) ||
129                      (!clipped && eventType == MouseEvent.MOUSE_PRESSED && mouseButton == MouseEvent.BUTTON1)) {
130                      
131                      return result;
132                  }
133              } else if (TabDisplayer.COMMAND_CLOSE == result && eventType == MouseEvent.MOUSE_RELEASED && isShowCloseButton()) {
134                  if ((modifiers & MouseEvent.SHIFT_DOWN_MASK) != 0) {
135                      return TabDisplayer.COMMAND_CLOSE_ALL;
136                  } else if ((modifiers & MouseEvent.ALT_DOWN_MASK) != 0 && mouseButton != MouseEvent.BUTTON2) {
137                      return TabDisplayer.COMMAND_CLOSE_ALL_BUT_THIS;
138                  }
139                  return result;
140              }
141         }
142         return null;
143     }
144
145     //********************** Subclass convenience API methods*****************
146

147     /**
148      * Convenience getter to determine if the current state includes the armed
149      * state (the mouse is in the tab the component is currently configured to
150      * render).
151      */

152     protected final boolean isArmed() {
153         return isPressed() || (state & TabState.ARMED) != 0;
154     }
155
156     /**
157      * Convenience getter to determine if the current state includes the active
158      * state (a component in the container or the container itself has keyboard
159      * focus)
160      */

161     protected final boolean isActive() {
162         return (state & TabState.ACTIVE) != 0;
163     }
164
165     /**
166      * Convenience getter to determine if the current state includes the pressed
167      * state (the mouse is in the tab this component is currently configured to
168      * render, and the mouse button is currently down)
169      */

170     protected final boolean isPressed() {
171         return (state & TabState.PRESSED) != 0;
172     }
173
174     /**
175      * Convenience getter to determine if the current state includes the
176      * selected state (the tab this component is currently configured to render
177      * is the selected tab in a container)
178      */

179     protected final boolean isSelected() {
180         return (state & TabState.SELECTED) != 0;
181     }
182
183     /**
184      * Convenience getter to determine if the current state includes the
185      * right-clipped state (the right hand side of the tab is not visible).
186      */

187     protected final boolean isClipRight() {
188         return (state & TabState.CLIP_RIGHT) != 0;
189     }
190
191     /**
192      * Convenience getter to determine if the current state includes the
193      * left-clipped state (the right hand side of the tab is not visible).
194      */

195     protected final boolean isClipLeft() {
196         return (state & TabState.CLIP_LEFT) != 0;
197     }
198
199     /**
200      * Convenience getter to determine if the current state indicates
201      * that the renderer is currently configured as the leftmost (non-clipped).
202      */

203     protected final boolean isLeftmost() {
204         return (state & TabState.LEFTMOST) != 0;
205     }
206
207     /**
208      * Convenience getter to determine if the current state indicates
209      * that the renderer is currently configured as the rightmost (non-clipped).
210      */

211     protected final boolean isRightmost() {
212         return (state & TabState.RIGHTMOST) != 0;
213     }
214     
215     protected final boolean isAttention() {
216         return (state & TabState.ATTENTION) != 0;
217     }
218
219     /**
220      * Convenience getter to determine if the current state indicates
221      * that the renderer is currently configured appears to the left of
222      * the selected tab.
223      */

224     protected final boolean isNextTabSelected() {
225         return (state & TabState.BEFORE_SELECTED) != 0;
226     }
227
228     /**
229      * Convenience getter to determine if the current state indicates
230      * that the renderer is currently configured appears to the left of
231      * the armed tab.
232      */

233     protected final boolean isNextTabArmed() {
234         return (state & TabState.BEFORE_ARMED) != 0;
235     }
236
237     /**
238      * Convenience getter to determine if the current state indicates
239      * that the renderer is currently configured appears to the right of
240      * the selected tab.
241      */

242     protected final boolean isPreviousTabSelected() {
243         return (state & TabState.AFTER_SELECTED) != 0;
244     }
245
246     public Dimension getPadding() {
247         return new Dimension(padding);
248     }
249
250     private int phash = -1;
251
252     /** Set the state of the renderer, in preparation for painting it or evaluating a condition
253      * (such as the position of the close button) for which it must be correctly configured).
254      * This method will call stateChanged(), allowing the renderer to reconfigure itself if
255      * necessary, when the state changes.
256      *
257      * @param state
258      */

259     protected final void setState(int state) {
260         //System.err.println("Renderer SetState " + TabState.stateToString(state));
261
boolean needChange = this.state != state;
262         if (needChange) {
263             int old = this.state;
264             //Set the state value here, so isArmed(), etc. will return
265
//correct values in stateChanged(), so subclasses can set
266
//up colors correctly
267
this.state = state;
268             int newState = stateChanged(old, state);
269             if ((newState & this.state) != state) {
270                 this.state = state;
271                 throw new IllegalStateException JavaDoc("StateChanged may add, but not remove bits from the " +
272                         "state bitmask. Expected state: " + TabState.stateToString(
273                         state) + " but got " + TabState.stateToString(this.state));
274             }
275             this.state = newState;
276         }
277     }
278
279     /**
280      * Returns the state as set up in getRendererComponent
281      */

282     public final int getState() {
283         return state;
284     }
285
286     /**
287      * Implementation of getRendererComponent from TabCellRenderer. This
288      * method is final, and will configure the text, bounds and icon correctly
289      * according to the passed values, and call setState to set the state of the
290      * tab. Implementers must implement <code>stateChanged()</code> to handle
291      * any changes (background color, border, etc) necessary to reflect the
292      * current state as returned by <code>getState()</code>.
293      */

294     public final javax.swing.JComponent JavaDoc getRendererComponent(TabData data,
295                                                              Rectangle bounds,
296                                                              int state) {
297         setBounds(bounds);
298         setText(data.getText());
299         setIcon(data.getIcon());
300         setState(state);
301         return this;
302     }
303
304     //***************SPI METHODS********************************************
305
/*
306      * Implementations of this method <strong>may not remove</strong> state bits
307      * that were passed in. A runtime check of the result will be performed,
308      * and in the case that some states were removed, a runtime exception will
309      * be thrown after this method exits.
310      */

311
312     protected int stateChanged(int oldState, int newState) {
313         Color bg = isSelected() ?
314                 isActive() ?
315                 getSelectedActivatedBackground() : getSelectedBackground() :
316                 UIManager.getColor("control");
317         Color fg = isSelected() ?
318                 isActive() ?
319                 getSelectedActivatedForeground() : getSelectedForeground() :
320                 UIManager.getColor("textText");
321
322         if (isArmed() && isPressed() && (isClipLeft() || isClipRight())) {
323             //Create an armed appearance for clipped, pressed tabs, which will respond
324
//to mouseReleased, not mousePressed
325
bg = getSelectedActivatedBackground();
326             fg = getSelectedActivatedForeground();
327         }
328
329         if (isClipLeft()) {
330             setIcon(null);
331             setBorder(leftBorder);
332         } else if (isClipRight()) {
333             setBorder(rightBorder);
334         } else {
335             setBorder(normalBorder);
336         }
337
338         setBackground(bg);
339         setForeground(fg);
340         return newState;
341     }
342
343     /** Overridden to be a no-op for performance reasons */
344     public void revalidate() {
345         //do nothing - performance
346
}
347
348     /** Overridden to be a no-op for performance reasons */
349     public void repaint() {
350         //do nothing - performance
351
}
352
353     /** Overridden to be a no-op for performance reasons */
354     public void validate() {
355         //do nothing - performance
356
}
357
358     /** Overridden to be a no-op for performance reasons */
359     public void repaint(long tm) {
360         //do nothing - performance
361
}
362
363     /** Overridden to be a no-op for performance reasons */
364     public void repaint(long tm, int x, int y, int w, int h) {
365         //do nothing - performance
366
}
367
368     /** Overridden to be a no-op for performance reasons */
369     protected final void firePropertyChange(String JavaDoc s, Object JavaDoc a, Object JavaDoc b) {
370         //do nothing - performance
371
}
372
373     /** Overridden to be a no-op for performance reasons */
374     public final void addHierarchyBoundsListener(HierarchyBoundsListener JavaDoc hbl) {
375         //do nothing
376
}
377
378     /** Overridden to be a no-op for performance reasons */
379     public final void addHierarchyListener(HierarchyListener JavaDoc hl) {
380         //do nothing
381
}
382
383     /** Overridden to be a no-op for performance reasons */
384     public final void addContainerListener(ContainerListener JavaDoc cl) {
385         //do nothing
386
}
387
388     /**
389      * Overridden to paint the interior of the polygon if the border is an instance of TabPainter.
390      */

391     public void paintComponent(Graphics g) {
392         g.setColor(getBackground());
393         if (getBorder() instanceof TabPainter) {
394             ((TabPainter) getBorder()).paintInterior(g, this);
395         }
396         paintIconAndText(g);
397     }
398
399     /** Return non-zero to shift the text up or down by the specified number of pixels when painting.
400      *
401      * @return A positive or negative number of pixels
402      */

403     protected int getCaptionYAdjustment() {
404         return -1;
405     }
406
407     /** Return non-zero to shift the icon up or down by the specified number of pixels when painting.
408      *
409      * @return A positive or negative number of pixels
410      */

411     protected int getIconYAdjustment() {
412         return -1;
413     }
414
415     /**
416      * Actually paints the icon and text (using the lightweight HTML renderer)
417      *
418      * @param g The graphics context
419      */

420     protected void paintIconAndText(Graphics g) {
421         g.setFont(getFont());
422         FontMetrics fm = g.getFontMetrics(getFont());
423         //Find out what height we need
424
int txtH = fm.getHeight();
425         Insets ins = getInsets();
426         //find out the available height
427
int availH = getHeight() - (ins.top + ins.bottom);
428         int txtY;
429         if (availH > txtH) {
430             txtY = txtH + ins.top + ((availH / 2) - (txtH / 2)) - 3;
431         } else {
432             txtY = txtH + ins.top;
433         }
434         int txtX;
435
436         int centeringToAdd = getPixelsToAddToSelection() != 0 ?
437                 getPixelsToAddToSelection() / 2 : 0;
438
439         Icon icon = getIcon();
440         //Check the icon non-null and height (see TabData.NO_ICON for why)
441
if (!isClipLeft() && icon != null && icon.getIconWidth() > 0
442                 && icon.getIconHeight() > 0) {
443             int iconY;
444             if (availH > icon.getIconHeight()) {
445                 //add 2 to make sure icon top pixels are not cut off by outline
446
iconY = ins.top
447                         + ((availH / 2) - (icon.getIconHeight() / 2))
448                         + 2;
449             } else {
450                 //add 2 to make sure icon top pixels are not cut off by outline
451
iconY = ins.top + 2;
452             }
453             int iconX = ins.left + centeringToAdd;
454
455             iconY += getIconYAdjustment();
456
457             icon.paintIcon(this, g, iconX, iconY);
458             txtX = iconX + icon.getIconWidth() + getIconTextGap();
459         } else {
460             txtX = ins.left + centeringToAdd;
461         }
462
463         if (icon != null && icon.getIconWidth() == 0) {
464             //Add some spacing so the text isn't flush for, e.g., the
465
//welcome screen tab
466
txtX += 5;
467         }
468
469         txtY += getCaptionYAdjustment();
470         
471         //Get the available horizontal pixels for text
472
int txtW = getWidth() - (txtX + ins.right);
473         if (isClipLeft()) {
474             //fiddle with the string to get "...blah"
475
String JavaDoc s = preTruncateString(getText(), g, txtW - 4); //subtract 4 so it's not flush w/ tab edge
476
HtmlRenderer.renderString(s, g, txtX, txtY, txtW, txtH, getFont(),
477                               getForeground(), HtmlRenderer.STYLE_CLIP, true);
478         } else {
479             String JavaDoc s;
480             if (isClipRight()) {
481                 //Jano wants to always show a "..." for cases where a tab is truncated,
482
//even if we've really painted all the text.
483
s = getText() + "..."; //NOI18N
484
} else {
485                 s = getText();
486             }
487             HtmlRenderer.renderString(s, g, txtX, txtY, txtW, txtH, getFont(),
488                               getForeground(), HtmlRenderer.STYLE_TRUNCATE, true);
489         }
490     }
491
492     static String JavaDoc preTruncateString(String JavaDoc s, Graphics g, int availPixels) {
493         if (s.length() < 3) {
494             return s;
495         }
496         s = stripHTML(s);
497         if (s.length() < 2) {
498             return "..." + s; //NOI18N
499
}
500         FontMetrics fm = g.getFontMetrics();
501         int dotsWidth = fm.stringWidth("..."); //NOI18N
502
int beginIndex = s.length() - 2;
503         String JavaDoc test = s.substring(beginIndex);
504         String JavaDoc result = test;
505         while (fm.stringWidth(test) + dotsWidth < availPixels) {
506             beginIndex--;
507             if (beginIndex <= 0) {
508                 break;
509             } else {
510                 result = test;
511                 test = s.substring(beginIndex);
512             }
513         }
514         return "..." + result; //NOI18N
515
}
516
517     static boolean isHTML(String JavaDoc s) {
518         boolean result = s.startsWith("<html>")
519                 || s.startsWith("<HTML>"); //NOI18N
520
return result;
521     }
522
523     static String JavaDoc stripHTML(String JavaDoc s) {
524         if (isHTML(s)) {
525             StringBuffer JavaDoc result = new StringBuffer JavaDoc(s.length());
526             char[] c = s.toCharArray();
527             boolean inTag = false;
528             for (int i = 0; i < c.length; i++) {
529                 //XXX need to handle entity includes
530
boolean wasInTag = inTag;
531                 if (!inTag) {
532                     if (c[i] == '<') {
533                         inTag = true;
534                     }
535                 } else {
536                     if (c[i] == '>') {
537                         inTag = false;
538                     }
539                 }
540                 if (!inTag && wasInTag == inTag) {
541                     result.append(c[i]);
542                 }
543             }
544             return result.toString();
545         } else {
546             return s;
547         }
548     }
549
550     /**
551      * Get the shape of the tab. The implementation here will check if the
552      * border is an instance of TabPainter, and if so, use the polygon it
553      * returns, translating it to the position of the passed-in rectangle. If
554      * you are subclassing but do not intend to use TabPainter, you need to
555      * override this method
556      */

557     public Polygon getTabShape(int tabState, Rectangle bounds) {
558         setBounds(bounds);
559         setState(tabState);
560         if (getBorder() instanceof TabPainter) {
561             TabPainter pb = (TabPainter) getBorder();
562             Polygon p = pb.getInteriorPolygon(this);
563             p.translate(bounds.x, bounds.y);
564             return p;
565         } else {
566             //punt and return the bounds as a polygon - what else to do?
567
return new Polygon(new int[]{
568                 bounds.x, bounds.x + bounds.width - 1,
569                 bounds.x + bounds.width - 1, bounds.x}, new int[]{
570                     bounds.y, bounds.y, bounds.y + bounds.height - 1,
571                     bounds.y + bounds.height - 1}, 4);
572         }
573     }
574
575
576
577
578     public Color getSelectedBackground() {
579         Color base = UIManager.getColor("control"); //NOI18N
580
Color towards = UIManager.getColor("controlHighlight"); //NOI18N
581

582         if (base == null) {
583             base = Color.GRAY;
584         }
585         if (towards == null) {
586             towards = Color.WHITE;
587         }
588
589         Color result = ColorUtil.adjustTowards(base, 30, towards);
590         return result;
591     }
592
593     public Color getSelectedActivatedBackground() {
594         return UIManager.getColor("TabRenderer.selectedActivatedBackground");
595     }
596
597     public Color getSelectedActivatedForeground() {
598         return UIManager.getColor("TabRenderer.selectedActivatedForeground");
599     }
600
601     public Color getSelectedForeground() {
602         return UIManager.getColor("TabRenderer.selectedForeground");
603     }
604
605     protected boolean inCloseButton() {
606         return (state & TabState.CLOSE_BUTTON_ARMED) != 0;
607     }
608
609     /**
610      * Subclasses which want to make the selected tab wider than it would otherwise be should return a value
611      * greater than 0 here. The default implementation returns 0.
612      */

613     public int getPixelsToAddToSelection() {
614         return 0;
615     }
616
617     private boolean supportsCloseButton(Border JavaDoc b) {
618         if (b instanceof TabPainter) {
619             return ((TabPainter) b).supportsCloseButton(this);
620         } else {
621             return false;
622         }
623     }
624 }
625
Popular Tags