switcher - auto selection + fixes
[idea/community.git] / platform / platform-api / src / com / intellij / ui / switcher / SwitchManager.java
1 /*
2  * Copyright 2000-2010 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.ui.switcher;
17
18 import com.intellij.ide.DataManager;
19 import com.intellij.openapi.actionSystem.KeyboardShortcut;
20 import com.intellij.openapi.actionSystem.Shortcut;
21 import com.intellij.openapi.components.ProjectComponent;
22 import com.intellij.openapi.keymap.Keymap;
23 import com.intellij.openapi.keymap.KeymapManager;
24 import com.intellij.openapi.keymap.KeymapManagerListener;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.ActionCallback;
27 import com.intellij.openapi.util.AsyncResult;
28 import com.intellij.openapi.util.Disposer;
29 import com.intellij.openapi.util.registry.Registry;
30 import com.intellij.openapi.wm.IdeFocusManager;
31 import com.intellij.openapi.wm.IdeFrame;
32 import com.intellij.util.Alarm;
33 import com.intellij.util.ui.UIUtil;
34 import org.jetbrains.annotations.NonNls;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37
38 import javax.swing.*;
39 import java.awt.*;
40 import java.awt.event.KeyEvent;
41 import java.util.HashSet;
42 import java.util.Set;
43
44 public class SwitchManager implements ProjectComponent, KeyEventDispatcher, KeymapManagerListener, Keymap.Listener  {
45
46   private Project myProject;
47
48   private SwitchingSession mySession;
49
50   private Set<Integer> myModifierCodes;
51
52   private Keymap myKeymap;
53   @NonNls private static final String SWITCH_UP = "SwitchUp";
54
55   private boolean myWaitingForAutoInitSession;
56   private Alarm myInitSessionAlarm = new Alarm();
57   private KeyEvent myAutoInitSessionEvent;
58
59   public SwitchManager(Project project) {
60     myProject = project;
61   }
62
63   public boolean dispatchKeyEvent(KeyEvent e) {
64     if (mySession != null && !mySession.isFinished()) return false;
65
66     Component c = e.getComponent();
67     Component frame = UIUtil.findUltimateParent(c);
68     if (frame instanceof IdeFrame) {
69       if (((IdeFrame)frame).getProject() != myProject) return false;
70     }
71
72     if (e.getID() != KeyEvent.KEY_PRESSED) {
73       if (myWaitingForAutoInitSession) {
74         cancelWaitingForAutoInit();
75       }
76       return false;
77     }
78
79     if (myModifierCodes != null && myModifierCodes.contains(e.getKeyCode())) {
80       if (areAllModifiersPressed(e.getModifiers())) {
81         myWaitingForAutoInitSession = true;
82         myAutoInitSessionEvent = e;
83         myInitSessionAlarm.addRequest(new Runnable() {
84           public void run() {
85             IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new Runnable() {
86               public void run() {
87                 if (myWaitingForAutoInitSession) {
88                   tryToInitSessionFromFocus(null);
89                 }
90               }
91             });
92           }
93         }, Registry.intValue("actionSystem.keyGestureHoldTime"));
94       }
95     } else {
96       if (myWaitingForAutoInitSession) {
97         cancelWaitingForAutoInit();
98       }
99     }
100
101     return false;
102   }
103
104   private ActionCallback tryToInitSessionFromFocus(@Nullable SwitchTarget preselected) {
105     if (mySession != null && !mySession.isFinished()) return new ActionCallback.Rejected();
106
107     Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
108     SwitchProvider provider = SwitchProvider.KEY.getData(DataManager.getInstance().getDataContext(owner));
109     if (provider != null) {
110       return initSession(new SwitchingSession(this, provider, myAutoInitSessionEvent, preselected));
111     }
112
113     return new ActionCallback.Rejected();
114   }
115
116   private void cancelWaitingForAutoInit() {
117     myWaitingForAutoInitSession = false;
118     myAutoInitSessionEvent = null;
119     myInitSessionAlarm.cancelAllRequests();
120   }
121
122   public void activeKeymapChanged(Keymap keymap) {
123     KeymapManager mgr = KeymapManager.getInstance();
124
125     if (myKeymap != null) {
126       myKeymap.removeShortcutChangeListener(this);
127     }
128
129     myKeymap = mgr.getActiveKeymap();
130     myKeymap.addShortcutChangeListener(this);
131
132     onShortcutChanged(SWITCH_UP);
133   }
134
135   public void onShortcutChanged(String actionId) {
136     if (!SWITCH_UP.equals(actionId)) return;
137
138     Shortcut[] shortcuts = myKeymap.getShortcuts(SWITCH_UP);
139     myModifierCodes = null;
140
141     if (shortcuts.length > 0) {
142       for (Shortcut each : shortcuts) {
143         if (each instanceof KeyboardShortcut) {
144           KeyboardShortcut kbs = (KeyboardShortcut)each;
145           KeyStroke stroke = kbs.getFirstKeyStroke();
146           myModifierCodes = getModifiersCodes(stroke.getModifiers());
147           break;
148         }
149       }
150     }
151   }
152
153   private boolean areAllModifiersPressed(int modifiers) {
154     int mask = 0;
155     for (Integer each : myModifierCodes) {
156       if (each == KeyEvent.VK_SHIFT) {
157         mask |= KeyEvent.SHIFT_MASK;
158       }
159
160       if (each == KeyEvent.VK_CONTROL) {
161         mask |= KeyEvent.CTRL_MASK;
162       }
163
164       if (each == KeyEvent.VK_META) {
165         mask |= KeyEvent.META_MASK;
166       }
167
168       if (each == KeyEvent.VK_ALT) {
169         mask |= KeyEvent.ALT_MASK;
170       }
171     }
172
173     return (modifiers ^ mask) == 0;
174   }
175
176   private Set<Integer> getModifiersCodes(int modifiers) {
177     Set<Integer> codes = new HashSet<Integer>();
178     if ((modifiers & KeyEvent.SHIFT_MASK) > 0) {
179       codes.add(KeyEvent.VK_SHIFT);
180     }
181     if ((modifiers & KeyEvent.CTRL_MASK) > 0) {
182       codes.add(KeyEvent.VK_CONTROL);
183     }
184
185     if ((modifiers & KeyEvent.META_MASK) > 0) {
186       codes.add(KeyEvent.VK_META);
187     }
188
189     if ((modifiers & KeyEvent.ALT_MASK) > 0) {
190       codes.add(KeyEvent.VK_ALT);
191     }
192
193     return codes;
194   }
195
196   public void initComponent() {
197     KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this);
198     KeymapManager kmMgr = KeymapManager.getInstance();
199     kmMgr.addKeymapManagerListener(this);
200
201     activeKeymapChanged(kmMgr.getActiveKeymap());
202   }
203
204   public void disposeComponent() {
205     KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this);
206     KeymapManager.getInstance().removeKeymapManagerListener(this);
207
208     if (myKeymap != null) {
209       myKeymap.removeShortcutChangeListener(this);
210       myKeymap = null;
211     }
212   }
213
214   public static SwitchManager getInstance(Project project) {
215     return project != null ? project.getComponent(SwitchManager.class) : null;
216   }
217
218   public SwitchingSession getSession() {
219     return mySession;
220   }
221
222   public ActionCallback initSession(SwitchingSession session) {
223     disposeSession(mySession);
224     mySession = session;
225     return new ActionCallback.Done();
226   }
227
228   private void disposeSession(SwitchingSession session) {
229     if (mySession != null) {
230       Disposer.dispose(session);
231       mySession = null;
232     }
233   }
234
235   public void projectOpened() {
236   }
237
238   public void projectClosed() {
239   }
240
241   @NotNull
242   public String getComponentName() {
243     return "ViewSwitchManager";
244   }
245
246   public boolean isSessionActive() {
247     return mySession != null && !mySession.isFinished();
248   }
249
250   public ActionCallback applySwitch() {
251     final ActionCallback result = new ActionCallback();
252     if (isSessionActive()) {
253       mySession.finish().doWhenDone(new AsyncResult.Handler<SwitchTarget>() {
254         public void run(final SwitchTarget switchTarget) {
255           mySession = null;
256           IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(new Runnable() {
257             public void run() {
258               tryToInitSessionFromFocus(switchTarget).doWhenProcessed(new Runnable() {
259                 public void run() {
260                   result.setDone();
261                 }
262               });
263             }
264           });
265         }
266       });
267     } else {
268       result.setDone();
269     }
270
271     return result;
272   }
273
274   public boolean canApplySwitch() {
275     return isSessionActive() && mySession.isSelectionWasMoved();
276   }
277
278   public void resetSession() {
279     disposeSession(mySession);
280   }
281 }