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.swing.tabcontrol;
043:
044: import java.awt.AWTEvent;
045: import java.awt.Component;
046: import java.awt.Point;
047: import java.awt.Toolkit;
048: import java.awt.event.AWTEventListener;
049: import java.awt.event.KeyEvent;
050: import java.awt.event.MouseEvent;
051: import javax.swing.JComponent;
052: import javax.swing.Popup;
053: import javax.swing.PopupFactory;
054: import javax.swing.SwingUtilities;
055: import javax.swing.event.MouseInputListener;
056: import org.netbeans.swing.popupswitcher.SwitcherTable;
057: import org.netbeans.swing.popupswitcher.SwitcherTableItem;
058:
059: /**
060: * Represents Popup for "Document switching" which is shown after an user clicks
061: * the down-arrow button in tabcontrol displayer.
062: *
063: * @author mkrauskopf
064: */
065: final class ButtonPopupSwitcher implements MouseInputListener,
066: AWTEventListener {
067:
068: /**
069: * Reference to the popup object currently showing the default instance, if
070: * it is visible
071: */
072: private static Popup popup;
073:
074: /**
075: * Reference to the focus owner when addNotify was called. This is the
076: * component that received the mouse event, so it's what we need to listen
077: * on to update the selected cell as the user drags the mouse
078: */
079: private Component invokingComponent = null;
080:
081: /**
082: * Time of invocation, used to determine if a mouse release is delayed long
083: * enough from a mouse press that it should close the popup, instead of
084: * assuming the user wants move-and-click behavior instead of
085: * drag-and-click behavior
086: */
087: private long invocationTime = -1;
088:
089: /** Indicating whether a popup is shown? */
090: private static boolean shown;
091:
092: private SwitcherTable pTable;
093:
094: private int x;
095: private int y;
096:
097: /**
098: * Creates and shows the popup with given <code>items</code>. When user
099: * choose an item <code>SwitcherTableItem.Activatable.activate()</code> is
100: * called. So what exactly happens depends on the concrete
101: * <code>SwitcherTableItem.Activatable</code> implementation. A popup appears
102: * on <code>x</code>, <code>y</code> coordinates.
103: */
104: public static void selectItem(JComponent owner,
105: SwitcherTableItem[] items, int x, int y) {
106: ButtonPopupSwitcher switcher = new ButtonPopupSwitcher(items,
107: x, y);
108: switcher.doSelect(owner);
109: }
110:
111: /** Creates a new instance of TabListPanel */
112: private ButtonPopupSwitcher(SwitcherTableItem items[], int x, int y) {
113: this .pTable = new SwitcherTable(items, y);
114: this .x = x - (int) pTable.getPreferredSize().getWidth();
115: this .y = y + 1;
116: }
117:
118: private void doSelect(JComponent owner) {
119: invokingComponent = owner;
120: invokingComponent.addMouseListener(this );
121: invokingComponent.addMouseMotionListener(this );
122: pTable.addMouseListener(this );
123: pTable.addMouseMotionListener(this );
124:
125: Toolkit.getDefaultToolkit().addAWTEventListener(this ,
126: AWTEvent.MOUSE_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
127: popup = PopupFactory.getSharedInstance().getPopup(
128: invokingComponent, pTable, x, y);
129: popup.show();
130: shown = true;
131: invocationTime = System.currentTimeMillis();
132: }
133:
134: /**
135: * Returns true if popup is displayed.
136: *
137: * @return True if a popup was closed.
138: */
139: public static boolean isShown() {
140: return shown;
141: }
142:
143: /**
144: * Clean up listners and hide popup.
145: */
146: private synchronized void hideCurrentPopup() {
147: pTable.removeMouseListener(this );
148: pTable.removeMouseMotionListener(this );
149: Toolkit.getDefaultToolkit().removeAWTEventListener(this );
150: if (invokingComponent != null) {
151: invokingComponent.removeMouseListener(this );
152: invokingComponent.removeMouseMotionListener(this );
153: invokingComponent = null;
154: }
155: if (popup != null) {
156: // Issue 41121 - use invokeLater to allow any pending event
157: // processing against the popup contents to run before the popup is
158: // hidden
159: SwingUtilities.invokeLater(new PopupHider(popup));
160: popup = null;
161: shown = false;
162: }
163: }
164:
165: /**
166: * Runnable which hides the popup in a subsequent event queue loop. This
167: * is to avoid problems with BasicToolbarUI, which will try to process
168: * events on the component after it has been hidden and throw exceptions.
169: *
170: * @see http://www.netbeans.org/issues/show_bug.cgi?id=41121
171: */
172: private class PopupHider implements Runnable {
173: private Popup toHide;
174:
175: public PopupHider(Popup popup) {
176: toHide = popup;
177: }
178:
179: public void run() {
180: toHide.hide();
181: toHide = null;
182: }
183: }
184:
185: public void mouseMoved(MouseEvent e) {
186: Point p = e.getPoint();
187: // It may have occured on the button that invoked the tabtable
188: if (e.getSource() != this ) {
189: p = SwingUtilities.convertPoint((Component) e.getSource(),
190: p, pTable);
191: }
192: if (pTable.contains(p)) {
193: int row = pTable.rowAtPoint(p);
194: int col = pTable.columnAtPoint(p);
195: pTable.changeSelection(row, col, false, false);
196: } else {
197: pTable.clearSelection();
198: }
199: e.consume();
200: }
201:
202: public void mousePressed(MouseEvent e) {
203: Point p = e.getPoint();
204: p = SwingUtilities.convertPoint((Component) e.getSource(), p,
205: pTable);
206: if (pTable.contains(p)) {
207: final SwitcherTableItem item = pTable.getSelectedItem();
208: if (item != null) {
209: item.activate();
210: hideCurrentPopup();
211: e.consume();
212: }
213: }
214: }
215:
216: public void mouseReleased(MouseEvent e) {
217: if (e.getSource() == invokingComponent) {
218: long time = System.currentTimeMillis();
219: if (time - invocationTime > 500) {
220: mousePressed(e);
221: }
222: }
223: e.consume();
224: }
225:
226: public void mouseClicked(MouseEvent e) {
227: e.consume();
228: }
229:
230: public void mouseEntered(MouseEvent e) {
231: mouseDragged(e);
232: e.consume();
233: }
234:
235: public void mouseExited(MouseEvent e) {
236: pTable.clearSelection();
237: e.consume();
238: }
239:
240: //MouseMotionListener
241: public void mouseDragged(MouseEvent e) {
242: mouseMoved(e);
243: e.consume();
244: }
245:
246: /**
247: * Was mouse upon the popup table when mouse action had been taken.
248: */
249: private boolean onSwitcherTable(MouseEvent e) {
250: Point p = e.getPoint();
251: //#118828
252: if (!(e.getSource() instanceof Component)) {
253: return false;
254: }
255:
256: p = SwingUtilities.convertPoint((Component) e.getSource(), p,
257: pTable);
258: return pTable.contains(p);
259: }
260:
261: /**
262: * Popup should be closed under some circumstances. Namely when mouse is
263: * pressed or released outside of popup or when key is pressed during the
264: * time popup is visible.
265: */
266: public void eventDispatched(AWTEvent event) {
267: if (event.getSource() == this ) {
268: return;
269: }
270: if (event instanceof MouseEvent) {
271: if (event.getID() == MouseEvent.MOUSE_RELEASED) {
272: long time = System.currentTimeMillis();
273: // check if button was just slowly clicked
274: if (time - invocationTime > 500) {
275: if (!onSwitcherTable((MouseEvent) event)) {
276: // Don't take any chances
277: hideCurrentPopup();
278: }
279: }
280: } else if (event.getID() == MouseEvent.MOUSE_PRESSED) {
281: if (!onSwitcherTable((MouseEvent) event)) {
282: // Don't take any chances
283: if (event.getSource() != invokingComponent) {
284: // If it's the invoker, don't do anything - it will
285: // generate another call to invoke(), which will do the
286: // hiding - if we do it here, it will get shown again
287: // when the button processes the event
288: hideCurrentPopup();
289: }
290: }
291: }
292: } else if (event instanceof KeyEvent) {
293: if (event.getID() == KeyEvent.KEY_PRESSED) {
294: Toolkit.getDefaultToolkit()
295: .removeAWTEventListener(this);
296: hideCurrentPopup();
297: }
298: }
299: }
300: }
|