KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > windows > view > ui > KeyboardPopupSwitcher


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.core.windows.view.ui;
21
22 import java.awt.Dimension JavaDoc;
23 import java.awt.KeyboardFocusManager JavaDoc;
24 import java.awt.Rectangle JavaDoc;
25 import java.awt.event.ActionEvent JavaDoc;
26 import java.awt.event.ActionListener JavaDoc;
27 import java.awt.event.InputEvent JavaDoc;
28 import java.awt.event.KeyEvent JavaDoc;
29 import java.awt.event.WindowEvent JavaDoc;
30 import java.awt.event.WindowFocusListener JavaDoc;
31 import javax.swing.AbstractAction JavaDoc;
32 import javax.swing.JWindow JavaDoc;
33 import javax.swing.SwingUtilities JavaDoc;
34 import javax.swing.Timer JavaDoc;
35 import org.netbeans.core.windows.WindowManagerImpl;
36 import org.netbeans.core.windows.actions.RecentViewListAction;
37 import org.netbeans.swing.popupswitcher.SwitcherTable;
38 import org.netbeans.swing.popupswitcher.SwitcherTableItem;
39 import org.openide.awt.StatusDisplayer;
40 import org.openide.util.Utilities;
41 import org.openide.windows.WindowManager;
42
43 /**
44  * Represents Popup for "Keyboard document switching" which is shown after
45  * pressing Ctrl+Tab (or alternatively Ctrl+`).
46  * If an user releases a <code>releaseKey</code> in <code>TIME_TO_SHOW</code> ms
47  * the popup won't show at all. Instead immediate switching will happen.
48  *
49  * @author mkrauskopf
50  */

51 public final class KeyboardPopupSwitcher implements WindowFocusListener JavaDoc {
52     
53     /** Number of milliseconds to show popup if interruption didn't happen. */
54     private static final int TIME_TO_SHOW = 200;
55     
56     /** Singleton */
57     private static KeyboardPopupSwitcher instance;
58     
59     /**
60      * Reference to the popup object currently showing the default instance, if
61      * it is visible
62      */

63     private static JWindow JavaDoc popup;
64     
65     /** Indicating whether a popup is shown? */
66     private static boolean shown;
67     
68     /**
69      * Invoke popup after a specified time. Can be interrupter if an user
70      * releases <code>triggerKey</code> key in that time.
71      */

72     private static Timer JavaDoc invokerTimer;
73     
74     /**
75      * Safely indicating whether a <code>invokerTimer</code> is running or not.
76      * isRunning() method doesn't work for us in all cases.
77      */

78     private static boolean invokerTimerRunning;
79     
80     /**
81      * Counts the number of <code>triggerKey</code> hits before the popup is
82      * shown (without first <code>triggerKey</code> press).
83      * If the <code>triggerKey</code> is pressed more than twice the
84      * popup will be shown immediately.
85      */

86     private static int hits;
87     
88     /**
89      * Current items to be shown in a popup. It is <code>static</code>, since
90      * there can be only one popup list at time.
91      */

92     private static SwitcherTableItem[] items;
93     
94     private SwitcherTable pTable;
95     
96     private static int triggerKey; // e.g. TAB
97
private static int reverseKey = KeyEvent.VK_SHIFT;
98     private static int releaseKey; // e.g. CTRL
99

100     private int x;
101     private int y;
102     
103     /** Indicates whether an item to be selected is previous or next one. */
104     private boolean fwd = true;
105         
106     /**
107      * Tries to process given <code>KeyEvent</code> and returns true is event
108      * was successfully processed/consumed.
109      */

110     public static boolean processShortcut(KeyEvent JavaDoc kev) {
111         WindowManagerImpl wmi = WindowManagerImpl.getInstance();
112         // don't perform when focus is dialog
113
if (!wmi.getMainWindow().isFocused() &&
114             !wmi.isSeparateWindow(KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow())) {
115             return false;
116         }
117
118         boolean isCtrlTab = kev.getKeyCode() == KeyEvent.VK_TAB &&
119                 kev.getModifiers() == InputEvent.CTRL_MASK;
120         boolean isCtrlShiftTab = kev.getKeyCode() == KeyEvent.VK_TAB &&
121                 kev.getModifiers() == (InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK);
122         if (KeyboardPopupSwitcher.isShown()) {
123             assert instance != null;
124             instance.processKeyEvent(kev);
125             // be sure that events is not processed further when popup is shown
126
kev.consume();
127             return true;
128         }
129         if ((isCtrlTab || isCtrlShiftTab)) { // && !KeyboardPopupSwitcher.isShown()
130
if (KeyboardPopupSwitcher.isAlive()) {
131                 KeyboardPopupSwitcher.processInterruption(kev);
132             } else {
133                 AbstractAction JavaDoc rva = new RecentViewListAction();
134                 rva.actionPerformed(new ActionEvent JavaDoc(kev.getSource(),
135                         ActionEvent.ACTION_PERFORMED, "C-TAB"));
136                 return true;
137             }
138             // consume all ctrl-(shift)-tab to avoid confusion about
139
// Ctrl-Tab events since those events are dedicated to document
140
// switching only
141
kev.consume();
142             return true;
143         }
144         if (kev.getKeyCode() == KeyEvent.VK_CONTROL && KeyboardPopupSwitcher.isAlive()) {
145             KeyboardPopupSwitcher.processInterruption(kev);
146             return true;
147         }
148         return false;
149     }
150     
151     /**
152      * Creates and shows the popup with given <code>items</code>. When user
153      * selects an item <code>SwitcherTableItem.Activatable.activate()</code> is
154      * called. So what exactly happens depends on the concrete
155      * <code>SwitcherTableItem.Activatable</code> implementation.
156      * Selection is made when user releases a <code>releaseKey</code> passed on
157      * as a parameter. If user releases the <code>releaseKey</code> before a
158      * specified time (<code>TIME_TO_SHOW</code>) expires the popup won't show
159      * at all and switch to the last used document will be performed
160      * immediately.
161      *
162      * A popup appears on <code>x</code>, <code>y</code> coordinates.
163      */

164     public static void selectItem(SwitcherTableItem items[], int releaseKey,
165             int triggerKey) {
166         // reject multiple invocations
167
if (invokerTimerRunning) {
168             return;
169         }
170         KeyboardPopupSwitcher.items = items;
171         KeyboardPopupSwitcher.releaseKey = releaseKey;
172         KeyboardPopupSwitcher.triggerKey = triggerKey;
173         invokerTimer = new Timer JavaDoc(TIME_TO_SHOW, new PopupInvoker());
174         invokerTimer.setRepeats(false);
175         invokerTimer.start();
176         invokerTimerRunning = true;
177     }
178     
179     /** Stop invoker timer and detach interrupter listener. */
180     private static void cleanupInterrupter() {
181         invokerTimerRunning = false;
182         if (invokerTimer != null) {
183             invokerTimer.stop();
184         }
185     }
186     
187     /**
188      * Serves to <code>invokerTimer</code>. Shows popup after specified time.
189      */

190     private static class PopupInvoker implements ActionListener JavaDoc {
191         /** Timer just hit the specified time_to_show */
192         public void actionPerformed(ActionEvent JavaDoc e) {
193             if (invokerTimerRunning) {
194                 cleanupInterrupter();
195                 instance = new KeyboardPopupSwitcher(hits + 1);
196                 instance.showPopup();
197             }
198         }
199     }
200     
201     /**
202      * Returns true if popup is displayed.
203      *
204      * @return True if a popup was closed.
205      */

206     public static boolean isShown() {
207         return shown;
208     }
209     
210     /**
211      * Indicate whether a popup will be or is shown. <em>Will be</em> means
212      * that a popup was already triggered by first Ctrl-Tab but TIME_TO_SHOW
213      * wasn't expires yet. <em>Is shown</em> means that a popup is really
214      * already shown on the screen.
215      */

216     private static boolean isAlive() {
217         return invokerTimerRunning || shown;
218     }
219     
220     /**
221      * Creates a new instance of KeyboardPopupSwitcher with initial selection
222      * set to <code>initialSelection</code>.
223      */

224     private KeyboardPopupSwitcher(int initialSelection) {
225         pTable = new SwitcherTable(items);
226         // Compute coordinates for popup to be displayed in center of screen
227
Dimension JavaDoc popupDim = pTable.getPreferredSize();
228         Rectangle JavaDoc screen = Utilities.getUsableScreenBounds();
229         this.x = screen.x + ((screen.width / 2) - (popupDim.width / 2));
230         this.y = screen.y + ((screen.height / 2) - (popupDim.height / 2));
231         // Set initial selection if there are at least two items in table
232
int cols = pTable.getColumnCount();
233         int rows = pTable.getRowCount();
234         assert cols > 0 : "There aren't any columns in the KeyboardPopupSwitcher's table"; // NOI18N
235
assert rows > 0 : "There aren't any rows in the KeyboardPopupSwitcher's table"; // NOI18N
236
changeTableSelection((rows > initialSelection) ? initialSelection :
237             initialSelection, 0);
238     }
239     
240     private void showPopup() {
241         if (!isShown()) {
242             // set popup to be always on top to be in front of all
243
// floating separate windows
244
popup = new JWindow JavaDoc();
245             popup.setAlwaysOnTop(true);
246             popup.getContentPane().add(pTable);
247             popup.setLocation(x, y);
248             popup.pack();
249             popup.setVisible(true);
250             // #82743 - on JDK 1.5 popup steals focus from main window for a millisecond,
251
// so we have to delay attaching of focus listener
252
SwingUtilities.invokeLater(new Runnable JavaDoc() {
253                 public void run () {
254                     WindowManager.getDefault().getMainWindow().
255                             addWindowFocusListener( KeyboardPopupSwitcher.this );
256                 }
257             });
258             shown = true;
259         }
260     }
261     
262     /**
263      * Prevents showing a popup if a user releases the <code>releaseKey</code>
264      * in time specified by <code>invokerTimer</code> (which is 200ms by
265      * default).
266      */

267     private static void processInterruption(KeyEvent JavaDoc kev) {
268         int keyCode = kev.getKeyCode();
269         if (keyCode == releaseKey && kev.getID() == KeyEvent.KEY_RELEASED) {
270             // if an user releases Ctrl-Tab before the time to show
271
// popup expires, don't show the popup at all and switch to
272
// the last used document immediately
273
cleanupInterrupter();
274             hits = 0;
275             AbstractAction JavaDoc rva = new RecentViewListAction();
276             rva.actionPerformed(new ActionEvent JavaDoc(kev.getSource(),
277                     ActionEvent.ACTION_PERFORMED,
278                     "immediately")); // NOI18N
279
kev.consume();
280         // #88931: Need to react to KEY_PRESSED, not KEY_RELEASED, to not miss the hit
281
} else if (keyCode == triggerKey
282                 && kev.getModifiers() == InputEvent.CTRL_MASK
283                 && kev.getID() == KeyEvent.KEY_PRESSED) {
284             // count number of trigger key hits before popup is shown
285
hits++;
286             kev.consume();
287             cleanupInterrupter();
288             instance = new KeyboardPopupSwitcher(hits + 1);
289             instance.showPopup();
290         }
291     }
292     
293     /** Handles given <code>KeyEvent</code>. */
294     private void processKeyEvent(KeyEvent JavaDoc kev) {
295         switch (kev.getID()) {
296             case KeyEvent.KEY_PRESSED:
297                 int code = kev.getKeyCode();
298                 if (code == reverseKey) {
299                     fwd = false;
300                 } else if (code == triggerKey) {
301                     int lastRowIdx = pTable.getRowCount() - 1;
302                     int lastColIdx = pTable.getColumnCount() - 1;
303                     int selRow = pTable.getSelectedRow();
304                     int selCol = pTable.getSelectedColumn();
305                     int row = selRow;
306                     int col = selCol;
307                     
308                     // MK initial alg.
309
if (fwd) {
310                         if (selRow >= lastRowIdx) {
311                             row = 0;
312                             col = (selCol >= lastColIdx ? 0 : ++col);
313                         } else {
314                             row++;
315                             if (pTable.getValueAt(row, col) == null) {
316                                 row = 0;
317                                 col = 0;
318                             }
319                         }
320                     } else {
321                         if (selRow == 0) {
322                             if (selCol == 0) {
323                                 col = lastColIdx;
324                                 row = pTable.getLastValidRow();
325                             } else {
326                                 col--;
327                                 row = lastRowIdx;
328                             }
329                         } else {
330                             row--;
331                         }
332                     }
333                     if (row >= 0 && col >= 0) {
334                         changeTableSelection(row, col);
335                     }
336                 }
337                 kev.consume();
338                 break;
339             case KeyEvent.KEY_RELEASED:
340                 code = kev.getKeyCode();
341                 if (code == reverseKey) {
342                     fwd = true;
343                     kev.consume();
344                 } else if (code == KeyEvent.VK_ESCAPE) { // XXX see above
345
cancelSwitching();
346                 } else if (code == releaseKey) {
347                     performSwitching();
348                 }
349                 break;
350                 }
351         }
352     
353     /** Changes table selection and sets status bar appropriately */
354     private void changeTableSelection(int row, int col) {
355         pTable.changeSelection(row, col, false, false);
356         // #95111: Defense agaist random selection failure
357
SwitcherTableItem item = pTable.getSelectedItem();
358         if (item != null) {
359             String JavaDoc statusText = item.getDescription();
360             StatusDisplayer.getDefault().setStatusText(
361                     statusText != null ? statusText : "");
362         }
363     }
364     
365     /**
366      * Cancels the popup if present, causing it to close without the active
367      * document being changed.
368      */

369     private void cancelSwitching() {
370         hideCurrentPopup();
371         StatusDisplayer.getDefault().setStatusText("");
372     }
373     
374     /** Switch to the currently selected document and close the popup. */
375     private void performSwitching() {
376         if (popup != null) {
377             // #90007: selection may be null if mouse is involved
378
SwitcherTableItem item = pTable.getSelectedItem();
379             if (item != null) {
380                 item.activate();
381             }
382         }
383         cancelSwitching();
384     }
385     
386     private synchronized void hideCurrentPopup() {
387         if (popup != null) {
388             // Issue 41121 - use invokeLater to allow any pending ev
389
// processing against the popup contents to run before the popup is
390
// hidden
391
SwingUtilities.invokeLater(new PopupHider(popup));
392         }
393     }
394
395     public void windowGainedFocus(WindowEvent JavaDoc e) {
396     }
397
398     public void windowLostFocus(WindowEvent JavaDoc e) {
399         //remove the switcher when the main window is deactivated,
400
//e.g. user pressed Ctrl+Esc on MS Windows which opens the Start menu
401
if (e.getOppositeWindow() != popup) {
402             cancelSwitching();
403         }
404     }
405     
406     /**
407      * Runnable which hides the popup in a subsequent ev queue loop. This is
408      * to avoid problems with BasicToolbarUI, which will try to process events
409      * on the component after it has been hidden and throw exceptions.
410      *
411      * @see http://www.netbeans.org/issues/show_bug.cgi?id=41121
412      */

413     private class PopupHider implements Runnable JavaDoc {
414         private JWindow JavaDoc toHide;
415         public PopupHider(JWindow JavaDoc popup) {
416             toHide = popup;
417         }
418         
419         public void run() {
420             toHide.setVisible(false);
421             shown = false;
422             hits = 0;
423             // part of #82743 fix
424
WindowManager.getDefault().getMainWindow().removeWindowFocusListener( KeyboardPopupSwitcher.this );
425         }
426     }
427 }
428
Popular Tags