001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.refactoring.spi.impl;
043:
044: import java.awt.Component;
045: import org.openide.windows.TopComponent;
046: import org.openide.util.Utilities;
047: import org.openide.ErrorManager;
048:
049: import javax.swing.*;
050: import javax.swing.event.ChangeEvent;
051: import javax.swing.event.ChangeListener;
052: import javax.swing.plaf.ColorUIResource;
053: import javax.swing.plaf.basic.BasicTabbedPaneUI;
054: import java.awt.*;
055: import java.awt.event.AWTEventListener;
056: import java.awt.event.MouseEvent;
057:
058: // #21380.
059: /**
060: * Copy of original CloseButtonTabbedPane from the NetBeans 3.4 winsys. Old code never dies.
061: *
062: * !!! jbecicka comment:
063: * !!! This class was copy/pasted from org.netbeans.core.output2.ui
064: * !!! See issue 44576
065: * !!! Remove this class as soon as issue 55845 is fixed
066: *
067: * @author Tran Duc Trung
068: *
069: */
070: final class CloseButtonTabbedPane extends JTabbedPane implements
071: ChangeListener, Runnable {
072:
073: private final Image closeTabImage = org.openide.util.Utilities
074: .loadImage("org/netbeans/modules/refactoring/api/resources/RefCloseTab.gif"); // NOI18N
075: private final Image closeTabInactiveImage = org.openide.util.Utilities
076: .loadImage("org/netbeans/modules/refactoring/api/resources/RefCloseTabInactive.gif"); // NOI18N
077:
078: public static final String PROP_CLOSE = "close"; // NOI18N
079:
080: CloseButtonTabbedPane() {
081: addChangeListener(this );
082: CloseButtonListener.install();
083: //Bugfix #28263: Disable focus.
084: setFocusable(false);
085: setBorder(javax.swing.BorderFactory.createEmptyBorder());
086: setFocusCycleRoot(true);
087: setFocusTraversalPolicy(new CBTPPolicy());
088: }
089:
090: private Component sel() {
091: Component c = getSelectedComponent();
092: return c == null ? this : c;
093: }
094:
095: private class CBTPPolicy extends FocusTraversalPolicy {
096: public Component getComponentAfter(Container aContainer,
097: Component aComponent) {
098: return sel();
099: }
100:
101: public Component getComponentBefore(Container aContainer,
102: Component aComponent) {
103: return sel();
104: }
105:
106: public Component getFirstComponent(Container aContainer) {
107: return sel();
108: }
109:
110: public Component getLastComponent(Container aContainer) {
111: return sel();
112: }
113:
114: public Component getDefaultComponent(Container aContainer) {
115: return sel();
116: }
117: }
118:
119: public int tabForCoordinate(int x, int y) {
120: return getUI().tabForCoordinate(this , x, y);
121: }
122:
123: private int pressedCloseButtonIndex = -1;
124: private int mouseOverCloseButtonIndex = -1;
125: private boolean draggedOut = false;
126:
127: public void stateChanged(ChangeEvent e) {
128: reset();
129: }
130:
131: public Component add(Component c) {
132: Component result = super .add(c);
133: String s = c.getName();
134: if (s != null) {
135: s += " "; // NOI18N
136: }
137: setTitleAt(getComponentCount() - 1, s);
138: return result;
139: }
140:
141: public void setTitleAt(int idx, String title) {
142: String nue = title.indexOf("</html>") != -1 ? //NOI18N
143: Utilities
144: .replaceString(title, "</html>", " </html>") //NOI18N
145: : title + " "; // NOI18N
146: if (!title.equals(getTitleAt(idx))) {
147: super .setTitleAt(idx, nue);
148: }
149: }
150:
151: private void reset() {
152: setMouseOverCloseButtonIndex(-1);
153: setPressedCloseButtonIndex(-1);
154: draggedOut = false;
155: }
156:
157: private Rectangle getCloseButtonBoundsAt(int i) {
158: Rectangle b = getBoundsAt(i);
159: if (b == null)
160: return null;
161: else {
162: b = new Rectangle(b);
163: fixGetBoundsAt(b);
164:
165: Dimension tabsz = getSize();
166: if (b.x + b.width >= tabsz.width
167: || b.y + b.height >= tabsz.height)
168: return null;
169:
170: return new Rectangle(b.x + b.width - 13, b.y + b.height / 2
171: - 5, 8, 8);
172: }
173: }
174:
175: /** Checks whether current L&F sets used keys for colors.
176: * If not puts default values. */
177: private static void checkUIColors() {
178: if (UIManager.getColor("Button.shadow") == null) { // NOI18N
179: UIManager.put("Button.shadow", // NOI18N
180: new ColorUIResource(153, 153, 153));
181: }
182: if (UIManager.getColor("Button.darkShadow") == null) { // NOI18N
183: UIManager.put("Button.darkShadow", // NOI18N
184: new ColorUIResource(102, 102, 102));
185: }
186: if (UIManager.getColor("Button.highlight") == null) { // NOI18N
187: UIManager.put("Button.highlight", // NOI18N
188: new ColorUIResource(Color.white));
189: }
190: if (UIManager.getColor("Button.background") == null) { // NOI18N
191: UIManager.put("Button.background", // NOI18N
192: new ColorUIResource(204, 204, 204));
193: }
194: }
195:
196: public void paint(Graphics g) {
197: super .paint(g);
198:
199: // #29181 All L&F doesn't support the colors used.
200: checkUIColors();
201:
202: // Have a look at
203: // http://ui.netbeans.org/docs/ui/closeButton/closeButtonUISpec.html
204: // to see how the buttons are specified to be drawn.
205:
206: int selectedIndex = getSelectedIndex();
207: for (int i = 0, n = getTabCount(); i < n; i++) {
208: Rectangle r = getCloseButtonBoundsAt(i);
209: if (r == null)
210: continue;
211:
212: if (i == pressedCloseButtonIndex && !draggedOut) {
213: g.setColor(UIManager.getColor("Button.shadow")); //NOI18N
214: g.fillRect(r.x, r.y, r.width, r.height);
215: }
216:
217: if (i != selectedIndex)
218: g.drawImage(closeTabInactiveImage, r.x + 2, r.y + 2,
219: this );
220: else
221: g.drawImage(closeTabImage, r.x + 2, r.y + 2, this );
222:
223: if (i == mouseOverCloseButtonIndex
224: || (i == pressedCloseButtonIndex && draggedOut)) {
225: g.setColor(UIManager.getColor("Button.darkShadow")); //NOI18N
226: g.drawRect(r.x, r.y, r.width, r.height);
227: g.setColor(i == selectedIndex ? UIManager
228: .getColor("Button.highlight") //NOI18N
229: : UIManager.getColor("Button.background")); //NOI18N
230: g.drawRect(r.x + 1, r.y + 1, r.width, r.height);
231:
232: // Draw the dots.
233: g.setColor(UIManager.getColor("Button.highlight")
234: .brighter()); //NOI18N
235: g.drawLine(r.x + r.width, r.y + 1, r.x + r.width,
236: r.y + 1);
237: g.drawLine(r.x + 1, r.y + r.height, r.x + 1, r.y
238: + r.height);
239: } else if (i == pressedCloseButtonIndex) {
240: g.setColor(UIManager.getColor("Button.shadow")); //NOI18N
241: g.drawRect(r.x, r.y, r.width, r.height);
242: g.setColor(i == selectedIndex ? UIManager
243: .getColor("Button.highlight") //NOI18N
244: : UIManager.getColor("Button.background")); //NOI18N
245: g.drawLine(r.x + 1, r.y + r.height + 1, r.x + r.width
246: + 1, r.y + r.height + 1);
247: g.drawLine(r.x + r.width + 1, r.y + 1, r.x + r.width
248: + 1, r.y + r.height + 1);
249:
250: // Draw the lines.
251: g.setColor(UIManager.getColor("Button.background")); //NOI18N
252: g.drawLine(r.x + 1, r.y + 1, r.x + r.width, r.y + 1);
253: g.drawLine(r.x + 1, r.y + 1, r.x + 1, r.y + r.height);
254: }
255: }
256: }
257:
258: private void setPressedCloseButtonIndex(int index) {
259: if (pressedCloseButtonIndex == index)
260: return;
261:
262: if (pressedCloseButtonIndex >= 0
263: && pressedCloseButtonIndex < getTabCount()) {
264: Rectangle r = getCloseButtonBoundsAt(pressedCloseButtonIndex);
265: repaint(r.x, r.y, r.width + 2, r.height + 2);
266:
267: JComponent c = (JComponent) getComponentAt(pressedCloseButtonIndex);
268: setToolTipTextAt(pressedCloseButtonIndex, c
269: .getToolTipText());
270: }
271:
272: pressedCloseButtonIndex = index;
273:
274: if (pressedCloseButtonIndex >= 0
275: && pressedCloseButtonIndex < getTabCount()) {
276: Rectangle r = getCloseButtonBoundsAt(pressedCloseButtonIndex);
277: repaint(r.x, r.y, r.width + 2, r.height + 2);
278: setMouseOverCloseButtonIndex(-1);
279: setToolTipTextAt(pressedCloseButtonIndex, null);
280: }
281: }
282:
283: private void setMouseOverCloseButtonIndex(int index) {
284: if (mouseOverCloseButtonIndex == index)
285: return;
286:
287: if (mouseOverCloseButtonIndex >= 0
288: && mouseOverCloseButtonIndex < getTabCount()) {
289: Rectangle r = getCloseButtonBoundsAt(mouseOverCloseButtonIndex);
290: repaint(r.x, r.y, r.width + 2, r.height + 2);
291: JComponent c = (JComponent) getComponentAt(mouseOverCloseButtonIndex);
292: setToolTipTextAt(mouseOverCloseButtonIndex, c
293: .getToolTipText());
294: }
295:
296: mouseOverCloseButtonIndex = index;
297:
298: if (mouseOverCloseButtonIndex >= 0
299: && mouseOverCloseButtonIndex < getTabCount()) {
300: Rectangle r = getCloseButtonBoundsAt(mouseOverCloseButtonIndex);
301: repaint(r.x, r.y, r.width + 2, r.height + 2);
302: setPressedCloseButtonIndex(-1);
303: setToolTipTextAt(mouseOverCloseButtonIndex, null);
304: }
305: }
306:
307: private void fireCloseRequest(Component c) {
308: firePropertyChange(PROP_CLOSE, null, c);
309: }
310:
311: public static void fixGetBoundsAt(Rectangle b) {
312: if (b.y < 0)
313: b.y = -b.y;
314: if (b.x < 0)
315: b.x = -b.x;
316: }
317:
318: public static int findTabForCoordinate(JTabbedPane tab, int x, int y) {
319: for (int i = 0; i < tab.getTabCount(); i++) {
320: Rectangle b = tab.getBoundsAt(i);
321: if (b != null) {
322: b = new Rectangle(b);
323: fixGetBoundsAt(b);
324:
325: if (b.contains(x, y)) {
326: return i;
327: }
328: }
329: }
330: return -1;
331: }
332:
333: boolean closingTab = false;
334:
335: public void doLayout() {
336: //JDK 1.5, Win L&F - we cannot do the layout synchronously when we've
337: //just removed a tab - the layout will have out of sync cache data
338: if (closingTab) {
339: SwingUtilities.invokeLater(this );
340: } else {
341: super .doLayout();
342: }
343: }
344:
345: public void run() {
346: doLayout();
347: closingTab = false;
348: repaint();
349: }
350:
351: protected void processMouseEvent(MouseEvent me) {
352: try {
353: super .processMouseEvent(me);
354: } catch (ArrayIndexOutOfBoundsException aioobe) {
355: //Bug in BasicTabbedPaneUI$Handler: The focusIndex field is not
356: //updated when tabs are removed programmatically, so it will try to
357: //repaint a tab that's not there
358: ErrorManager.getDefault().annotate(aioobe, "Suppressed " + //NOI18N
359: "AIOOBE bug in BasicTabbedPaneUI"); //NOI18N
360: ErrorManager.getDefault().notify(
361: ErrorManager.INFORMATIONAL, aioobe);
362: }
363: }
364:
365: private static class CloseButtonListener implements
366: AWTEventListener {
367: private static boolean installed = false;
368:
369: private CloseButtonListener() {
370: }
371:
372: private static synchronized void install() {
373: if (installed)
374: return;
375:
376: installed = true;
377: Toolkit.getDefaultToolkit().addAWTEventListener(
378: new CloseButtonListener(),
379: AWTEvent.MOUSE_EVENT_MASK
380: | AWTEvent.MOUSE_MOTION_EVENT_MASK);
381: }
382:
383: public void eventDispatched(AWTEvent ev) {
384: MouseEvent e = (MouseEvent) ev;
385: //#118828
386: if (!(ev.getSource() instanceof Component)) {
387: return;
388: }
389:
390: Component c = (Component) e.getSource();
391: while (c != null && !(c instanceof CloseButtonTabbedPane))
392: c = c.getParent();
393: if (c == null)
394: return;
395: final CloseButtonTabbedPane tab = (CloseButtonTabbedPane) c;
396:
397: Point p = SwingUtilities.convertPoint((Component) e
398: .getSource(), e.getPoint(), tab);
399:
400: if (e.getID() == MouseEvent.MOUSE_CLICKED) {
401: //Not interested in clicked, and it can cause an NPE
402: return;
403: }
404:
405: int index = findTabForCoordinate(tab, p.x, p.y);
406:
407: Rectangle r = null;
408: if (index >= 0)
409: r = tab.getCloseButtonBoundsAt(index);
410: if (r == null)
411: r = new Rectangle(0, 0, 0, 0);
412:
413: switch (e.getID()) {
414: case MouseEvent.MOUSE_PRESSED:
415: if (r.contains(p)) {
416: tab.setPressedCloseButtonIndex(index);
417: tab.draggedOut = false;
418: e.consume();
419: return;
420: }
421: break;
422:
423: case MouseEvent.MOUSE_RELEASED:
424: if (r.contains(p) && tab.pressedCloseButtonIndex >= 0) {
425: tab.closingTab = true;
426: Component tc = tab
427: .getComponentAt(tab.pressedCloseButtonIndex);
428: tab.reset();
429:
430: tab.fireCloseRequest(tc);
431: e.consume();
432: return;
433: } else {
434: tab.reset();
435: }
436: break;
437:
438: case MouseEvent.MOUSE_ENTERED:
439: break;
440:
441: case MouseEvent.MOUSE_EXITED:
442: //tab.reset();
443:
444: // XXX(-ttran) when the user clicks on the close button on
445: // an unfocused (internal) frame the focus is transferred
446: // to the frame and an unexpected MOUSE_EXITED event is
447: // fired. If we call reset() at every MOUSE_EXITED event
448: // then when the mouse button is released the tab is not
449: // closed. See bug #24450
450:
451: break;
452:
453: case MouseEvent.MOUSE_MOVED:
454: if (r.contains(p)) {
455: tab.setMouseOverCloseButtonIndex(index);
456: tab.draggedOut = false;
457: e.consume();
458: return;
459: } else if (tab.mouseOverCloseButtonIndex >= 0) {
460: tab.setMouseOverCloseButtonIndex(-1);
461: tab.draggedOut = false;
462: e.consume();
463: }
464: break;
465:
466: case MouseEvent.MOUSE_DRAGGED:
467: if (tab.pressedCloseButtonIndex >= 0) {
468: if (tab.draggedOut != !r.contains(p)) {
469: tab.draggedOut = !r.contains(p);
470: tab
471: .repaint(r.x, r.y, r.width + 2,
472: r.height + 2);
473: }
474: e.consume();
475: return;
476: }
477: break;
478: }
479: }
480: }
481: }
|