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.netbeans.swing.tabcontrol;
44
import java.awt.AWTEvent;
45
import java.awt.Component;
46
import java.awt.Point;
47
import java.awt.Toolkit;
48
import java.awt.event.AWTEventListener;
49
import java.awt.event.KeyEvent;
50
import java.awt.event.MouseEvent;
51
import javax.swing.JComponent;
52
import javax.swing.Popup;
53
import javax.swing.PopupFactory;
54
import javax.swing.SwingUtilities;
55
import javax.swing.event.MouseInputListener;
56
import org.netbeans.swing.popupswitcher.SwitcherTable;
57
import org.netbeans.swing.popupswitcher.SwitcherTableItem;
60
* Represents Popup for "Document switching" which is shown after an user clicks
61
* the down-arrow button in tabcontrol displayer.
65
final class ButtonPopupSwitcher
66
implements MouseInputListener, AWTEventListener {
69
* Reference to the popup object currently showing the default instance, if
72
private static Popup popup;
75
* Reference to the focus owner when addNotify was called. This is the
76
* component that received the mouse event, so it's what we need to listen
77
* on to update the selected cell as the user drags the mouse
79
private Component invokingComponent = null;
82
* Time of invocation, used to determine if a mouse release is delayed long
83
* enough from a mouse press that it should close the popup, instead of
84
* assuming the user wants move-and-click behavior instead of
85
* drag-and-click behavior
87
private long invocationTime = -1;
89
/** Indicating whether a popup is shown? */
90
private static boolean shown;
92
private SwitcherTable pTable;
98
* Creates and shows the popup with given <code>items</code>. When user
99
* 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.
104
public static void selectItem(JComponent owner, SwitcherTableItem[] items,
106
ButtonPopupSwitcher switcher
107
= new ButtonPopupSwitcher(items, x, y);
108
switcher.doSelect(owner);
111
/** Creates a new instance of TabListPanel */
112
private ButtonPopupSwitcher(SwitcherTableItem items[],
115
this.pTable = new SwitcherTable(items, y);
116
this.x = x - (int) pTable.getPreferredSize().getWidth();
120
private void doSelect(JComponent owner) {
121
invokingComponent = owner;
122
invokingComponent.addMouseListener(this);
123
invokingComponent.addMouseMotionListener(this);
124
pTable.addMouseListener(this);
125
pTable.addMouseMotionListener(this);
127
Toolkit.getDefaultToolkit().addAWTEventListener(this,
128
AWTEvent.MOUSE_EVENT_MASK
129
| AWTEvent.KEY_EVENT_MASK);
130
popup = PopupFactory.getSharedInstance().getPopup(
131
invokingComponent, pTable, x, y);
134
invocationTime = System.currentTimeMillis();
138
* Returns true if popup is displayed.
140
* @return True if a popup was closed.
142
public static boolean isShown() {
147
* Clean up listners and hide popup.
149
private synchronized void hideCurrentPopup() {
150
pTable.removeMouseListener(this);
151
pTable.removeMouseMotionListener(this);
152
Toolkit.getDefaultToolkit().removeAWTEventListener(this);
153
if (invokingComponent != null) {
154
invokingComponent.removeMouseListener(this);
155
invokingComponent.removeMouseMotionListener(this);
156
invokingComponent = null;
159
// Issue 41121 - use invokeLater to allow any pending event
160
// processing against the popup contents to run before the popup is
162
SwingUtilities.invokeLater(new PopupHider(popup));
169
* Runnable which hides the popup in a subsequent event queue loop. This
170
* is to avoid problems with BasicToolbarUI, which will try to process
171
* events on the component after it has been hidden and throw exceptions.
173
* @see http://www.netbeans.org/issues/show_bug.cgi?id=41121
175
private class PopupHider implements Runnable {
176
private Popup toHide;
177
public PopupHider(Popup popup) {
187
public void mouseMoved(MouseEvent e) {
188
Point p = e.getPoint();
189
// It may have occured on the button that invoked the tabtable
190
if (e.getSource() != this) {
191
p = SwingUtilities.convertPoint((Component) e.getSource(), p, pTable);
193
if (pTable.contains(p)) {
194
int row = pTable.rowAtPoint(p);
195
int col = pTable.columnAtPoint(p);
196
pTable.changeSelection(row, col, false, false);
198
pTable.clearSelection();
203
public void mousePressed(MouseEvent e) {
204
Point p = e.getPoint();
205
p = SwingUtilities.convertPoint((Component) e.getSource(), p, pTable);
206
if (pTable.contains(p)) {
207
final SwitcherTableItem item = pTable.getSelectedItem();
216
public void mouseReleased(MouseEvent e) {
217
if (e.getSource() == invokingComponent) {
218
long time = System.currentTimeMillis();
219
if (time - invocationTime > 500) {
226
public void mouseClicked(MouseEvent e) {
230
public void mouseEntered(MouseEvent e) {
235
public void mouseExited(MouseEvent e) {
236
pTable.clearSelection();
240
//MouseMotionListener
241
public void mouseDragged(MouseEvent e) {
247
* Was mouse upon the popup table when mouse action had been taken.
249
private boolean onSwitcherTable(MouseEvent e) {
250
Point p = e.getPoint();
252
if (! (e.getSource() instanceof Component)) {
256
p = SwingUtilities.convertPoint((Component) e.getSource(), p, pTable);
257
return pTable.contains(p);
261
* Popup should be closed under some circumstances. Namely when mouse is
262
* pressed or released outside of popup or when key is pressed during the
263
* time popup is visible.
265
public void eventDispatched(AWTEvent event) {
266
if (event.getSource() == this) {
269
if (event instanceof MouseEvent) {
270
if (event.getID() == MouseEvent.MOUSE_RELEASED) {
271
long time = System.currentTimeMillis();
272
// check if button was just slowly clicked
273
if (time - invocationTime > 500) {
274
if (!onSwitcherTable((MouseEvent) event)) {
275
// Don't take any chances
279
} else if (event.getID() == MouseEvent.MOUSE_PRESSED) {
280
if (!onSwitcherTable((MouseEvent) event)) {
281
// Don't take any chances
282
if (event.getSource() != invokingComponent) {
283
// If it's the invoker, don't do anything - it will
284
// generate another call to invoke(), which will do the
285
// hiding - if we do it here, it will get shown again
286
// when the button processes the event
291
} else if (event instanceof KeyEvent) {
292
if (event.getID() == KeyEvent.KEY_PRESSED) {
293
Toolkit.getDefaultToolkit().removeAWTEventListener(this);