2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.openide.awt;
44
import java.awt.AWTEvent;
45
import java.awt.Component;
46
import java.awt.Container;
47
import java.awt.Dimension;
48
import java.awt.FocusTraversalPolicy;
49
import java.awt.Graphics;
50
import java.awt.Image;
51
import java.awt.Point;
52
import java.awt.Rectangle;
53
import java.awt.Toolkit;
54
import javax.swing.plaf.UIResource;
55
import org.openide.util.Utilities;
56
import javax.swing.event.ChangeEvent;
57
import javax.swing.event.ChangeListener;
58
import java.awt.event.AWTEventListener;
59
import java.awt.event.MouseEvent;
60
import java.util.logging.Level;
61
import java.util.logging.Logger;
62
import javax.swing.JComponent;
63
import javax.swing.JTabbedPane;
64
import javax.swing.SwingUtilities;
65
import javax.swing.UIManager;
66
import org.openide.util.Exceptions;
69
* Copy of original CloseButtonTabbedPane from the NetBeans 3.4 winsys. Old code never dies.
71
* @author Tran Duc Trung
76
final class CloseButtonTabbedPane extends JTabbedPane {
78
private Image closeTabImage;
79
private Image closeTabPressedImage;
80
private Image closeTabMouseOverImage;
82
static final String PROP_CLOSE = "close";
84
CloseButtonTabbedPane() {
85
addChangeListener( new ChangeListener() {
86
public void stateChanged(ChangeEvent e) {
90
CloseButtonListener.install();
91
//Bugfix #28263: Disable focus.
93
setFocusCycleRoot(true);
94
setFocusTraversalPolicy(new CBTPPolicy());
97
private Component sel() {
98
Component c = getSelectedComponent();
99
return c == null ? this : c;
102
private class CBTPPolicy extends FocusTraversalPolicy {
103
public Component getComponentAfter(Container aContainer, Component aComponent) {
107
public Component getComponentBefore(Container aContainer, Component aComponent) {
111
public Component getFirstComponent(Container aContainer) {
115
public Component getLastComponent(Container aContainer) {
119
public Component getDefaultComponent(Container aContainer) {
124
private int pressedCloseButtonIndex = -1;
125
private int mouseOverCloseButtonIndex = -1;
126
private boolean draggedOut = false;
128
public Component add (Component c) {
129
Component result = super.add(c);
130
// #75317 - don't try to set the title if LF (such as Substance LF)
131
// is adding some custom UI components into tabbed pane
132
if (!(c instanceof UIResource)) {
133
String s = c.getName();
137
setTitleAt (getComponentCount() - 1, s);
142
public void setTitleAt(int idx, String title) {
143
String nue = title.indexOf("</html>") != -1 ? //NOI18N
144
Utilities.replaceString(title, "</html>", " </html>") //NOI18N
146
if (!title.equals(getTitleAt(idx))) {
147
super.setTitleAt(idx, nue);
151
private void reset() {
152
setMouseOverCloseButtonIndex(-1);
153
setPressedCloseButtonIndex(-1);
157
private Rectangle getCloseButtonBoundsAt(int i) {
158
Rectangle b = getBoundsAt(i);
162
b = new Rectangle(b);
165
Dimension tabsz = getSize();
166
if (b.x + b.width >= tabsz.width
167
|| b.y + b.height >= tabsz.height)
170
if (b.width == 0 || b.height == 0) {
173
if( (isWindowsVistaLaF() || isWindowsXPLaF() || isWindowsLaF()) && i == getSelectedIndex() ) {
176
} else if( isWindowsXPLaF() || isWindowsLaF() || isAquaLaF() ) {
179
if( i == getTabCount()-1 ) {
182
else if( isAquaLaF() )
185
return new Rectangle(b.x + b.width - 13,
186
b.y + b.height / 2 - 5,
193
private boolean isWindowsVistaLaF() {
194
String osName = System.getProperty ("os.name");
195
return osName.indexOf("Vista") >= 0
196
|| (osName.equals( "Windows NT (unknown)" ) && "6.0".equals( System.getProperty("os.version") ));
199
private boolean isWindowsXPLaF() {
200
Boolean isXP = (Boolean)Toolkit.getDefaultToolkit().
201
getDesktopProperty("win.xpstyle.themeActive"); //NOI18N
202
return isWindowsLaF() && (isXP == null ? false : isXP.booleanValue());
205
private boolean isWindowsLaF () {
206
String lfID = UIManager.getLookAndFeel().getID();
207
return lfID.endsWith("Windows"); //NOI18N
210
private boolean isAquaLaF() {
211
return "Aqua".equals( UIManager.getLookAndFeel().getID() );
214
private boolean isMetalLaF () {
215
String lfID = UIManager.getLookAndFeel().getID();
216
return "Metal".equals( lfID ); //NOI18N
219
public void paint(Graphics g) {
223
// http://ui.netbeans.org/docs/ui/closeButton/closeButtonUISpec.html
224
// to see how the buttons are specified to be drawn.
226
int selectedIndex = getSelectedIndex();
227
for (int i = 0, n = getTabCount(); i < n; i++) {
228
Rectangle r = getCloseButtonBoundsAt(i);
232
if (i == mouseOverCloseButtonIndex
233
|| (i == pressedCloseButtonIndex && draggedOut)) {
234
g.drawImage(getCloseTabMouseOverImage(), r.x, r.y , this);
235
} else if (i == pressedCloseButtonIndex) {
236
g.drawImage(getCloseTabPressedImage(), r.x, r.y , this);
238
g.drawImage(getCloseTabImage(), r.x, r.y , this);
243
private Image getCloseTabImage() {
244
if( null == closeTabImage ) {
245
if( isWindowsVistaLaF() ) {
246
closeTabImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/vista_close_enabled.png"); // NOI18N
247
} else if( isWindowsXPLaF() ) {
248
closeTabImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/xp_close_enabled.png"); // NOI18N
249
} else if( isWindowsLaF() ) {
250
closeTabImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/win_close_enabled.png"); // NOI18N
251
} else if( isAquaLaF() ) {
252
closeTabImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/mac_close_enabled.png"); // NOI18N
254
closeTabImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/metal_close_enabled.png"); // NOI18N
257
return closeTabImage;
260
private Image getCloseTabPressedImage() {
261
if( null == closeTabPressedImage ) {
262
if( isWindowsVistaLaF() ) {
263
closeTabPressedImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/vista_close_pressed.png"); // NOI18N
264
} else if( isWindowsXPLaF() ) {
265
closeTabPressedImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/xp_close_pressed.png"); // NOI18N
266
} else if( isWindowsLaF() ) {
267
closeTabPressedImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/win_close_pressed.png"); // NOI18N
268
} else if( isAquaLaF() ) {
269
closeTabPressedImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/mac_close_pressed.png"); // NOI18N
271
closeTabPressedImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/metal_close_pressed.png"); // NOI18N
274
return closeTabPressedImage;
277
private Image getCloseTabMouseOverImage() {
278
if( null == closeTabMouseOverImage ) {
279
if( isWindowsVistaLaF() ) {
280
closeTabMouseOverImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/vista_close_rollover.png"); // NOI18N
281
} else if( isWindowsXPLaF() ) {
282
closeTabMouseOverImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/xp_close_rollover.png"); // NOI18N
283
} else if( isWindowsLaF() ) {
284
closeTabMouseOverImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/win_close_rollover.png"); // NOI18N
285
} else if( isAquaLaF() ) {
286
closeTabMouseOverImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/mac_close_rollover.png"); // NOI18N
288
closeTabMouseOverImage = org.openide.util.Utilities.loadImage("org/openide/awt/resources/metal_close_rollover.png"); // NOI18N
291
return closeTabMouseOverImage;
294
private void setPressedCloseButtonIndex(int index) {
295
if (pressedCloseButtonIndex == index)
298
if (pressedCloseButtonIndex >= 0
299
&& pressedCloseButtonIndex < getTabCount()) {
300
Rectangle r = getCloseButtonBoundsAt(pressedCloseButtonIndex);
301
repaint(r.x, r.y, r.width + 2, r.height + 2);
303
JComponent c = _getJComponentAt(pressedCloseButtonIndex);
305
setToolTipTextAt(pressedCloseButtonIndex, c.getToolTipText());
308
pressedCloseButtonIndex = index;
310
if (pressedCloseButtonIndex >= 0
311
&& pressedCloseButtonIndex < getTabCount()) {
312
Rectangle r = getCloseButtonBoundsAt(pressedCloseButtonIndex);
313
repaint(r.x, r.y, r.width + 2, r.height + 2);
314
setMouseOverCloseButtonIndex(-1);
315
setToolTipTextAt(pressedCloseButtonIndex, null);
319
private void setMouseOverCloseButtonIndex(int index) {
320
if (mouseOverCloseButtonIndex == index)
323
if (mouseOverCloseButtonIndex >= 0
324
&& mouseOverCloseButtonIndex < getTabCount()) {
325
Rectangle r = getCloseButtonBoundsAt(mouseOverCloseButtonIndex);
326
repaint(r.x, r.y, r.width + 2, r.height + 2);
327
JComponent c = _getJComponentAt(mouseOverCloseButtonIndex);
329
setToolTipTextAt(mouseOverCloseButtonIndex, c.getToolTipText());
332
mouseOverCloseButtonIndex = index;
334
if (mouseOverCloseButtonIndex >= 0
335
&& mouseOverCloseButtonIndex < getTabCount()) {
336
Rectangle r = getCloseButtonBoundsAt(mouseOverCloseButtonIndex);
337
repaint(r.x, r.y, r.width + 2, r.height + 2);
338
setPressedCloseButtonIndex(-1);
339
setToolTipTextAt(mouseOverCloseButtonIndex, null);
343
private JComponent _getJComponentAt( int tabIndex ) {
344
Component c = getComponentAt( tabIndex );
345
return c instanceof JComponent ? (JComponent)c : null;
348
private void fireCloseRequest(Component c) {
349
firePropertyChange(PROP_CLOSE, null, c);
352
static void fixGetBoundsAt(Rectangle b) {
359
static int findTabForCoordinate(JTabbedPane tab, int x, int y) {
360
for (int i = 0; i < tab.getTabCount(); i++) {
361
Rectangle b = tab.getBoundsAt(i);
363
b = new Rectangle(b);
366
if (b.contains(x, y)) {
375
protected void processMouseEvent (MouseEvent me) {
377
super.processMouseEvent (me);
378
} catch (ArrayIndexOutOfBoundsException aioobe) {
379
//Bug in BasicTabbedPaneUI$Handler: The focusIndex field is not
380
//updated when tabs are removed programmatically, so it will try to
381
//repaint a tab that's not there
382
Exceptions.attachLocalizedMessage(aioobe,
383
"Suppressed AIOOBE bug in BasicTabbedPaneUI"); //NOI18N
384
Logger.getAnonymousLogger().log(Level.WARNING, null, aioobe);
389
private static class CloseButtonListener implements AWTEventListener
391
private static boolean installed = false;
393
private CloseButtonListener() {}
395
private static synchronized void install() {
400
Toolkit.getDefaultToolkit().addAWTEventListener(
401
new CloseButtonListener(),
402
AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
405
public void eventDispatched (AWTEvent ev) {
406
MouseEvent e = (MouseEvent) ev;
408
if (! (ev.getSource() instanceof Component)) {
413
Component c = (Component) e.getSource();
414
while (c != null && !(c instanceof CloseButtonTabbedPane))
418
final CloseButtonTabbedPane tab = (CloseButtonTabbedPane) c;
420
Point p = SwingUtilities.convertPoint((Component) e.getSource(),
424
if (e.getID() == MouseEvent.MOUSE_CLICKED) {
425
//Not interested in clicked, and it can cause an NPE
429
int index = findTabForCoordinate(tab, p.x, p.y);
433
r = tab.getCloseButtonBoundsAt(index);
435
r = new Rectangle(0,0,0,0);
438
case MouseEvent.MOUSE_PRESSED:
440
tab.setPressedCloseButtonIndex(index);
441
tab.draggedOut = false;
447
case MouseEvent.MOUSE_RELEASED:
448
if (r.contains(p) && tab.pressedCloseButtonIndex >= 0) {
450
tab.getComponentAt(tab.pressedCloseButtonIndex);
453
tab.fireCloseRequest(tc);
462
case MouseEvent.MOUSE_ENTERED:
465
case MouseEvent.MOUSE_EXITED:
468
// XXX(-ttran) when the user clicks on the close button on
469
// an unfocused (internal) frame the focus is transferred
470
// to the frame and an unexpected MOUSE_EXITED event is
471
// fired. If we call reset() at every MOUSE_EXITED event
472
// then when the mouse button is released the tab is not
473
// closed. See bug #24450
477
case MouseEvent.MOUSE_MOVED:
479
tab.setMouseOverCloseButtonIndex(index);
480
tab.draggedOut = false;
484
else if (tab.mouseOverCloseButtonIndex >= 0) {
485
tab.setMouseOverCloseButtonIndex(-1);
486
tab.draggedOut = false;
491
case MouseEvent.MOUSE_DRAGGED:
492
if (tab.pressedCloseButtonIndex >= 0) {
493
if (tab.draggedOut != !r.contains(p)) {
494
tab.draggedOut = !r.contains(p);
495
tab.repaint(r.x, r.y, r.width + 2+6, r.height + 2+6);