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.xml.schema.refactoring.ui;
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 public class CloseButtonTabbedPane extends JTabbedPane implements
071: ChangeListener, Runnable {
072:
073: public static final long serialVersionUID = 1L;
074: private final Image closeTabImage = org.openide.util.Utilities
075: .loadImage("org/netbeans/modules/refactoring/resources/RefCloseTab.gif"); // NOI18N
076: private final Image closeTabInactiveImage = org.openide.util.Utilities
077: .loadImage("org/netbeans/modules/refactoring/resources/RefCloseTabInactive.gif"); // NOI18N
078:
079: public static final String PROP_CLOSE = "close"; // NOI18N
080:
081: public CloseButtonTabbedPane() {
082: addChangeListener(this );
083: CloseButtonListener.install();
084: //Bugfix #28263: Disable focus.
085: setFocusable(false);
086: setBorder(javax.swing.BorderFactory.createEmptyBorder());
087: setFocusCycleRoot(true);
088: setFocusTraversalPolicy(new CBTPPolicy());
089: }
090:
091: private Component sel() {
092: Component c = getSelectedComponent();
093: return c == null ? this : c;
094: }
095:
096: private class CBTPPolicy extends FocusTraversalPolicy {
097: public Component getComponentAfter(Container aContainer,
098: Component aComponent) {
099: return sel();
100: }
101:
102: public Component getComponentBefore(Container aContainer,
103: Component aComponent) {
104: return sel();
105: }
106:
107: public Component getFirstComponent(Container aContainer) {
108: return sel();
109: }
110:
111: public Component getLastComponent(Container aContainer) {
112: return sel();
113: }
114:
115: public Component getDefaultComponent(Container aContainer) {
116: return sel();
117: }
118: }
119:
120: public int tabForCoordinate(int x, int y) {
121: return getUI().tabForCoordinate(this , x, y);
122: }
123:
124: private int pressedCloseButtonIndex = -1;
125: private int mouseOverCloseButtonIndex = -1;
126: private boolean draggedOut = false;
127:
128: public void stateChanged(ChangeEvent e) {
129: reset();
130: }
131:
132: public Component add(Component c) {
133: Component result = super .add(c);
134: String s = c.getName();
135: if (s != null) {
136: s += " "; // NOI18N
137: }
138: setTitleAt(getComponentCount() - 1, s);
139: return result;
140: }
141:
142: public void setTitleAt(int idx, String title) {
143: String nue = title.indexOf("</html>") != -1 ? //NOI18N
144: Utilities
145: .replaceString(title, "</html>", " </html>") //NOI18N
146: : title + " "; // NOI18N
147: if (!title.equals(getTitleAt(idx))) {
148: super .setTitleAt(idx, nue);
149: }
150: }
151:
152: private void reset() {
153: setMouseOverCloseButtonIndex(-1);
154: setPressedCloseButtonIndex(-1);
155: draggedOut = false;
156: }
157:
158: private Rectangle getCloseButtonBoundsAt(int i) {
159: Rectangle b = getBoundsAt(i);
160: if (b == null)
161: return null;
162: else {
163: b = new Rectangle(b);
164: fixGetBoundsAt(b);
165:
166: Dimension tabsz = getSize();
167: if (b.x + b.width >= tabsz.width
168: || b.y + b.height >= tabsz.height)
169: return null;
170:
171: return new Rectangle(b.x + b.width - 13, b.y + b.height / 2
172: - 5, 8, 8);
173: }
174: }
175:
176: /** Checks whether current L&F sets used keys for colors.
177: * If not puts default values. */
178: private static void checkUIColors() {
179: if (UIManager.getColor("Button.shadow") == null) { // NOI18N
180: UIManager.put("Button.shadow", // NOI18N
181: new ColorUIResource(153, 153, 153));
182: }
183: if (UIManager.getColor("Button.darkShadow") == null) { // NOI18N
184: UIManager.put("Button.darkShadow", // NOI18N
185: new ColorUIResource(102, 102, 102));
186: }
187: if (UIManager.getColor("Button.highlight") == null) { // NOI18N
188: UIManager.put("Button.highlight", // NOI18N
189: new ColorUIResource(Color.white));
190: }
191: if (UIManager.getColor("Button.background") == null) { // NOI18N
192: UIManager.put("Button.background", // NOI18N
193: new ColorUIResource(204, 204, 204));
194: }
195: }
196:
197: public void paint(Graphics g) {
198: super .paint(g);
199:
200: // #29181 All L&F doesn't support the colors used.
201: checkUIColors();
202:
203: // Have a look at
204: // http://ui.netbeans.org/docs/ui/closeButton/closeButtonUISpec.html
205: // to see how the buttons are specified to be drawn.
206:
207: int selectedIndex = getSelectedIndex();
208: for (int i = 0, n = getTabCount(); i < n; i++) {
209: Rectangle r = getCloseButtonBoundsAt(i);
210: if (r == null)
211: continue;
212:
213: if (i == pressedCloseButtonIndex && !draggedOut) {
214: g.setColor(UIManager.getColor("Button.shadow")); //NOI18N
215: g.fillRect(r.x, r.y, r.width, r.height);
216: }
217:
218: if (i != selectedIndex)
219: g.drawImage(closeTabInactiveImage, r.x + 2, r.y + 2,
220: this );
221: else
222: g.drawImage(closeTabImage, r.x + 2, r.y + 2, this );
223:
224: if (i == mouseOverCloseButtonIndex
225: || (i == pressedCloseButtonIndex && draggedOut)) {
226: g.setColor(UIManager.getColor("Button.darkShadow")); //NOI18N
227: g.drawRect(r.x, r.y, r.width, r.height);
228: g.setColor(i == selectedIndex ? UIManager
229: .getColor("Button.highlight") //NOI18N
230: : UIManager.getColor("Button.background")); //NOI18N
231: g.drawRect(r.x + 1, r.y + 1, r.width, r.height);
232:
233: // Draw the dots.
234: g.setColor(UIManager.getColor("Button.highlight")
235: .brighter()); //NOI18N
236: g.drawLine(r.x + r.width, r.y + 1, r.x + r.width,
237: r.y + 1);
238: g.drawLine(r.x + 1, r.y + r.height, r.x + 1, r.y
239: + r.height);
240: } else if (i == pressedCloseButtonIndex) {
241: g.setColor(UIManager.getColor("Button.shadow")); //NOI18N
242: g.drawRect(r.x, r.y, r.width, r.height);
243: g.setColor(i == selectedIndex ? UIManager
244: .getColor("Button.highlight") //NOI18N
245: : UIManager.getColor("Button.background")); //NOI18N
246: g.drawLine(r.x + 1, r.y + r.height + 1, r.x + r.width
247: + 1, r.y + r.height + 1);
248: g.drawLine(r.x + r.width + 1, r.y + 1, r.x + r.width
249: + 1, r.y + r.height + 1);
250:
251: // Draw the lines.
252: g.setColor(UIManager.getColor("Button.background")); //NOI18N
253: g.drawLine(r.x + 1, r.y + 1, r.x + r.width, r.y + 1);
254: g.drawLine(r.x + 1, r.y + 1, r.x + 1, r.y + r.height);
255: }
256: }
257: }
258:
259: private void setPressedCloseButtonIndex(int index) {
260: if (pressedCloseButtonIndex == index)
261: return;
262:
263: if (pressedCloseButtonIndex >= 0
264: && pressedCloseButtonIndex < getTabCount()) {
265: Rectangle r = getCloseButtonBoundsAt(pressedCloseButtonIndex);
266: repaint(r.x, r.y, r.width + 2, r.height + 2);
267:
268: JComponent c = (JComponent) getComponentAt(pressedCloseButtonIndex);
269: setToolTipTextAt(pressedCloseButtonIndex, c
270: .getToolTipText());
271: }
272:
273: pressedCloseButtonIndex = index;
274:
275: if (pressedCloseButtonIndex >= 0
276: && pressedCloseButtonIndex < getTabCount()) {
277: Rectangle r = getCloseButtonBoundsAt(pressedCloseButtonIndex);
278: repaint(r.x, r.y, r.width + 2, r.height + 2);
279: setMouseOverCloseButtonIndex(-1);
280: setToolTipTextAt(pressedCloseButtonIndex, null);
281: }
282: }
283:
284: private void setMouseOverCloseButtonIndex(int index) {
285: if (mouseOverCloseButtonIndex == index)
286: return;
287:
288: if (mouseOverCloseButtonIndex >= 0
289: && mouseOverCloseButtonIndex < getTabCount()) {
290: Rectangle r = getCloseButtonBoundsAt(mouseOverCloseButtonIndex);
291: repaint(r.x, r.y, r.width + 2, r.height + 2);
292: JComponent c = (JComponent) getComponentAt(mouseOverCloseButtonIndex);
293: setToolTipTextAt(mouseOverCloseButtonIndex, c
294: .getToolTipText());
295: }
296:
297: mouseOverCloseButtonIndex = index;
298:
299: if (mouseOverCloseButtonIndex >= 0
300: && mouseOverCloseButtonIndex < getTabCount()) {
301: Rectangle r = getCloseButtonBoundsAt(mouseOverCloseButtonIndex);
302: repaint(r.x, r.y, r.width + 2, r.height + 2);
303: setPressedCloseButtonIndex(-1);
304: setToolTipTextAt(mouseOverCloseButtonIndex, null);
305: }
306: }
307:
308: private void fireCloseRequest(Component c) {
309: firePropertyChange(PROP_CLOSE, null, c);
310: }
311:
312: public static void fixGetBoundsAt(Rectangle b) {
313: if (b.y < 0)
314: b.y = -b.y;
315: if (b.x < 0)
316: b.x = -b.x;
317: }
318:
319: public static int findTabForCoordinate(JTabbedPane tab, int x, int y) {
320: for (int i = 0; i < tab.getTabCount(); i++) {
321: Rectangle b = tab.getBoundsAt(i);
322: if (b != null) {
323: b = new Rectangle(b);
324: fixGetBoundsAt(b);
325:
326: if (b.contains(x, y)) {
327: return i;
328: }
329: }
330: }
331: return -1;
332: }
333:
334: boolean closingTab = false;
335:
336: public void doLayout() {
337: //JDK 1.5, Win L&F - we cannot do the layout synchronously when we've
338: //just removed a tab - the layout will have out of sync cache data
339: if (closingTab) {
340: SwingUtilities.invokeLater(this );
341: } else {
342: super .doLayout();
343: }
344: }
345:
346: public void run() {
347: doLayout();
348: closingTab = false;
349: repaint();
350: }
351:
352: protected void processMouseEvent(MouseEvent me) {
353: try {
354: super .processMouseEvent(me);
355: } catch (ArrayIndexOutOfBoundsException aioobe) {
356: //Bug in BasicTabbedPaneUI$Handler: The focusIndex field is not
357: //updated when tabs are removed programmatically, so it will try to
358: //repaint a tab that's not there
359: ErrorManager.getDefault().annotate(aioobe, "Suppressed " + //NOI18N
360: "AIOOBE bug in BasicTabbedPaneUI"); //NOI18N
361: ErrorManager.getDefault().notify(
362: ErrorManager.INFORMATIONAL, aioobe);
363: }
364: }
365:
366: private static class CloseButtonListener implements
367: AWTEventListener {
368: private static boolean installed = false;
369:
370: private CloseButtonListener() {
371: }
372:
373: private static synchronized void install() {
374: if (installed)
375: return;
376:
377: installed = true;
378: Toolkit.getDefaultToolkit().addAWTEventListener(
379: new CloseButtonListener(),
380: AWTEvent.MOUSE_EVENT_MASK
381: | AWTEvent.MOUSE_MOTION_EVENT_MASK);
382: }
383:
384: public void eventDispatched(AWTEvent ev) {
385: MouseEvent e = (MouseEvent) ev;
386:
387: Component c = (Component) e.getSource();
388: while (c != null && !(c instanceof CloseButtonTabbedPane))
389: c = c.getParent();
390: if (c == null)
391: return;
392: final CloseButtonTabbedPane tab = (CloseButtonTabbedPane) c;
393:
394: Point p = SwingUtilities.convertPoint((Component) e
395: .getSource(), e.getPoint(), tab);
396:
397: if (e.getID() == MouseEvent.MOUSE_CLICKED) {
398: //Not interested in clicked, and it can cause an NPE
399: return;
400: }
401:
402: int index = findTabForCoordinate(tab, p.x, p.y);
403:
404: Rectangle r = null;
405: if (index >= 0)
406: r = tab.getCloseButtonBoundsAt(index);
407: if (r == null)
408: r = new Rectangle(0, 0, 0, 0);
409:
410: switch (e.getID()) {
411: case MouseEvent.MOUSE_PRESSED:
412: if (r.contains(p)) {
413: tab.setPressedCloseButtonIndex(index);
414: tab.draggedOut = false;
415: e.consume();
416: return;
417: }
418: break;
419:
420: case MouseEvent.MOUSE_RELEASED:
421: if (r.contains(p) && tab.pressedCloseButtonIndex >= 0) {
422: tab.closingTab = true;
423: Component tc = tab
424: .getComponentAt(tab.pressedCloseButtonIndex);
425: tab.reset();
426:
427: tab.fireCloseRequest(tc);
428: e.consume();
429: return;
430: } else {
431: tab.reset();
432: }
433: break;
434:
435: case MouseEvent.MOUSE_ENTERED:
436: break;
437:
438: case MouseEvent.MOUSE_EXITED:
439: //tab.reset();
440:
441: // XXX(-ttran) when the user clicks on the close button on
442: // an unfocused (internal) frame the focus is transferred
443: // to the frame and an unexpected MOUSE_EXITED event is
444: // fired. If we call reset() at every MOUSE_EXITED event
445: // then when the mouse button is released the tab is not
446: // closed. See bug #24450
447:
448: break;
449:
450: case MouseEvent.MOUSE_MOVED:
451: if (r.contains(p)) {
452: tab.setMouseOverCloseButtonIndex(index);
453: tab.draggedOut = false;
454: e.consume();
455: return;
456: } else if (tab.mouseOverCloseButtonIndex >= 0) {
457: tab.setMouseOverCloseButtonIndex(-1);
458: tab.draggedOut = false;
459: e.consume();
460: }
461: break;
462:
463: case MouseEvent.MOUSE_DRAGGED:
464: if (tab.pressedCloseButtonIndex >= 0) {
465: if (tab.draggedOut != !r.contains(p)) {
466: tab.draggedOut = !r.contains(p);
467: tab
468: .repaint(r.x, r.y, r.width + 2,
469: r.height + 2);
470: }
471: e.consume();
472: return;
473: }
474: break;
475: }
476: }
477: }
478: }
|