ea45e3b5664865f8d530926aea6efa714a2af337
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ui / popup / WizardPopup.java
blobea45e3b5664865f8d530926aea6efa714a2af337
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.ui.popup;
18 import com.intellij.ide.DataManager;
19 import com.intellij.openapi.actionSystem.DataProvider;
20 import com.intellij.openapi.actionSystem.PlatformDataKeys;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.ui.popup.*;
24 import com.intellij.openapi.util.Disposer;
25 import com.intellij.openapi.util.Pair;
26 import com.intellij.ui.PopupBorder;
27 import com.intellij.ui.ScreenUtil;
28 import com.intellij.ui.popup.list.ListPopupImpl;
29 import com.intellij.ui.popup.tree.TreePopupImpl;
30 import com.intellij.ui.popup.util.MnemonicsSearch;
31 import com.intellij.ui.speedSearch.ElementFilter;
32 import com.intellij.ui.speedSearch.SpeedSearch;
33 import org.jetbrains.annotations.NonNls;
34 import org.jetbrains.annotations.NotNull;
36 import javax.swing.*;
37 import java.awt.*;
38 import java.awt.event.*;
39 import java.util.Collections;
41 public abstract class WizardPopup extends AbstractPopup implements ActionListener, ElementFilter {
42 private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.WizardPopup");
44 private static final int AUTO_POPUP_DELAY = 750;
45 private static final Dimension MAX_SIZE = new Dimension(Integer.MAX_VALUE, 600);
47 protected static final int STEP_X_PADDING = 2;
49 private final WizardPopup myParent;
51 protected final PopupStep<Object> myStep;
52 protected WizardPopup myChild;
54 private final Timer myAutoSelectionTimer = new Timer(AUTO_POPUP_DELAY, this);
56 private MnemonicsSearch myMnemonicsSearch;
57 private Object myParentValue;
59 private Point myLastOwnerPoint;
60 private Window myOwnerWindow;
61 private MyComponentAdapter myOwnerListener;
63 private final ActionMap myActionMap = new ActionMap();
64 private final InputMap myInputMap = new InputMap();
66 public WizardPopup(PopupStep<Object> aStep) {
67 this(null, aStep);
70 public WizardPopup(JBPopup aParent, PopupStep<Object> aStep) {
71 myParent = (WizardPopup) aParent;
72 myStep = aStep;
74 mySpeedSearch.setEnabled(myStep.isSpeedSearchEnabled());
76 final JComponent content = createContent();
78 JScrollPane scrollPane = new JScrollPane(content);
79 scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
80 scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
81 scrollPane.getHorizontalScrollBar().setBorder(null);
83 scrollPane.getActionMap().get("unitScrollLeft").setEnabled(false);
84 scrollPane.getActionMap().get("unitScrollRight").setEnabled(false);
86 scrollPane.setBorder(null);
88 final Project project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext());
89 init(project, scrollPane, getPreferredFocusableComponent(), true, true, true, true, null,
90 false, aStep.getTitle(), null, true, null, false, null, null, null, false, null, true, false, true, null, 0f,
91 null, true, false, new Component[0], null, true, Collections.<Pair<ActionListener, KeyStroke>>emptyList(), null);
93 registerAction("disposeAll", KeyEvent.VK_ESCAPE, InputEvent.SHIFT_MASK, new AbstractAction() {
94 public void actionPerformed(ActionEvent e) {
95 if (mySpeedSearch.isHoldingFilter()) {
96 mySpeedSearch.reset();
98 else {
99 disposeAll();
104 AbstractAction goBackAction = new AbstractAction() {
105 public void actionPerformed(ActionEvent e) {
106 goBack();
110 registerAction("goBack3", KeyEvent.VK_ESCAPE, 0, goBackAction);
112 myMnemonicsSearch = new MnemonicsSearch(this) {
113 protected void select(Object value) {
114 onSelectByMnemonic(value);
122 private void disposeAll() {
123 WizardPopup root = PopupDispatcher.getActiveRoot();
124 disposeAllParents(null);
125 root.getStep().canceled();
128 public void goBack() {
129 if (mySpeedSearch.isHoldingFilter()) {
130 mySpeedSearch.reset();
131 return;
134 if (myParent != null) {
135 myParent.disposeChildren();
137 else {
138 disposeAll();
142 protected abstract JComponent createContent();
144 public void dispose() {
145 super.dispose();
147 myAutoSelectionTimer.stop();
149 PopupDispatcher.unsetShowing(this);
150 PopupDispatcher.clearRootIfNeeded(this);
153 if (myOwnerWindow != null && myOwnerListener != null) {
154 myOwnerWindow.removeComponentListener(myOwnerListener);
159 public void disposeChildren() {
160 if (myChild != null) {
161 myChild.disposeChildren();
162 Disposer.dispose(myChild);
163 myChild = null;
167 public void show(final Component owner, final int aScreenX, final int aScreenY, final boolean considerForcedXY) {
168 LOG.assertTrue (!isDisposed());
170 Rectangle targetBounds = new Rectangle(new Point(aScreenX, aScreenY), getContent().getPreferredSize());
171 ScreenUtil.moveRectangleToFitTheScreen(targetBounds);
173 if (getParent() != null) {
174 if (getParent().getBounds().intersects(targetBounds)) {
175 targetBounds.x = getParent().getBounds().x - targetBounds.width - STEP_X_PADDING;
179 if (getParent() == null) {
180 PopupDispatcher.setActiveRoot(this);
182 else {
183 PopupDispatcher.setShowing(this);
186 LOG.assertTrue (!isDisposed(), "Disposed popup, parent="+getParent());
187 super.show(owner, targetBounds.x, targetBounds.y, true);
190 protected void afterShow() {
191 super.afterShow();
192 registerAutoMove();
194 if (!myFocusTrackback.isMustBeShown()) {
195 cancel();
199 private void registerAutoMove() {
200 if (myOwner != null) {
201 myOwnerWindow = SwingUtilities.getWindowAncestor(myOwner);
202 if (myOwnerWindow != null) {
203 myLastOwnerPoint = myOwnerWindow.getLocationOnScreen();
204 myOwnerListener = new MyComponentAdapter();
205 myOwnerWindow.addComponentListener(myOwnerListener);
210 private void processParentWindowMoved() {
211 if (isDisposed()) return;
213 final Point newOwnerPoint = myOwnerWindow.getLocationOnScreen();
215 int deltaX = myLastOwnerPoint.x - newOwnerPoint.x;
216 int deltaY = myLastOwnerPoint.y - newOwnerPoint.y;
218 myLastOwnerPoint = newOwnerPoint;
220 final Window wnd = SwingUtilities.getWindowAncestor(getContent());
221 final Point current = wnd.getLocationOnScreen();
223 setLocation(new Point(current.x - deltaX, current.y - deltaY));
226 protected abstract JComponent getPreferredFocusableComponent();
228 public void cancel(InputEvent e) {
229 super.cancel(e);
230 disposeChildren();
231 Disposer.dispose(this);
232 getStep().canceled();
235 @Override
236 public boolean isCancelKeyEnabled() {
237 return super.isCancelKeyEnabled() && !mySpeedSearch.isHoldingFilter();
240 protected void disposeAllParents(InputEvent e) {
241 myDisposeEvent = e;
242 dispose();
243 if (myParent != null) {
244 myParent.disposeAllParents(null);
248 public final void registerAction(@NonNls String aActionName, int aKeyCode, int aModifier, Action aAction) {
249 myInputMap.put(KeyStroke.getKeyStroke(aKeyCode, aModifier), aActionName);
250 myActionMap.put(aActionName, aAction);
253 public final void registerAction(@NonNls String aActionName, KeyStroke keyStroke, Action aAction) {
254 myInputMap.put(keyStroke, aActionName);
255 myActionMap.put(aActionName, aAction);
258 protected abstract InputMap getInputMap();
260 protected abstract ActionMap getActionMap();
262 protected final void setParentValue(Object parentValue) {
263 myParentValue = parentValue;
266 @NotNull
267 protected MyContentPanel createContentPanel(final boolean resizable, final PopupBorder border, final boolean isToDrawMacCorner) {
268 return new MyContainer(resizable, border, isToDrawMacCorner);
271 private static class MyContainer extends MyContentPanel implements DataProvider {
273 private MyContainer(final boolean resizable, final PopupBorder border, final boolean drawMacCorner) {
274 super(resizable, border, drawMacCorner);
275 setOpaque(true);
276 setFocusCycleRoot(true);
279 public Object getData(String dataId) {
280 return null;
283 public Dimension getPreferredSize() {
284 return computeNotBiggerDimension(super.getPreferredSize());
287 private static Dimension computeNotBiggerDimension(Dimension ofContent) {
288 int resultWidth = ofContent.width > MAX_SIZE.width ? MAX_SIZE.width : ofContent.width;
289 int resultHeight = ofContent.height > MAX_SIZE.height ? MAX_SIZE.height : ofContent.height;
291 if (ofContent.height > MAX_SIZE.height) {
292 resultWidth += new JScrollPane().getVerticalScrollBar().getPreferredSize().getWidth();
295 return new Dimension(resultWidth, resultHeight);
299 public WizardPopup getParent() {
300 return myParent;
303 public PopupStep getStep() {
304 return myStep;
307 public final void dispatch(KeyEvent event) {
308 if (event.getID() != KeyEvent.KEY_PRESSED && event.getID() != KeyEvent.KEY_RELEASED) {
309 return;
312 if (event.getID() == KeyEvent.KEY_PRESSED) {
313 final KeyStroke stroke = KeyStroke.getKeyStroke(event.getKeyCode(), event.getModifiers(), false);
314 if (proceedKeyEvent(event, stroke)) return;
317 if (event.getID() == KeyEvent.KEY_RELEASED) {
318 final KeyStroke stroke = KeyStroke.getKeyStroke(event.getKeyCode(), event.getModifiers(), true);
319 proceedKeyEvent(event, stroke);
320 return;
323 myMnemonicsSearch.process(event);
324 mySpeedSearch.process(event);
326 if (event.isConsumed()) return;
327 process(event);
330 private boolean proceedKeyEvent(KeyEvent event, KeyStroke stroke) {
331 if (myInputMap.get(stroke) != null) {
332 final Action action = myActionMap.get(myInputMap.get(stroke));
333 if (action != null && action.isEnabled()) {
334 action.actionPerformed(new ActionEvent(getContent(), event.getID(), "", event.getWhen(), event.getModifiers()));
335 return true;
338 return false;
341 protected void process(KeyEvent aEvent) {
345 public Rectangle getBounds() {
346 return new Rectangle(getContent().getLocationOnScreen(), getContent().getSize());
349 protected static WizardPopup createPopup(WizardPopup parent, PopupStep step, Object parentValue) {
350 if (step instanceof ListPopupStep) {
351 return new ListPopupImpl(parent, (ListPopupStep)step, parentValue);
353 else if (step instanceof TreePopupStep) {
354 return new TreePopupImpl(parent, (TreePopupStep)step, parentValue);
356 else {
357 throw new IllegalArgumentException(step.getClass().toString());
361 public final void actionPerformed(ActionEvent e) {
362 myAutoSelectionTimer.stop();
363 if (getStep().isAutoSelectionEnabled()) {
364 onAutoSelectionTimer();
368 protected final void restartTimer() {
369 if (!myAutoSelectionTimer.isRunning()) {
370 myAutoSelectionTimer.start();
372 else {
373 myAutoSelectionTimer.restart();
377 protected final void stopTimer() {
378 myAutoSelectionTimer.stop();
381 protected void onAutoSelectionTimer() {
385 public boolean shouldBeShowing(Object value) {
386 if (!myStep.isSpeedSearchEnabled()) return true;
387 SpeedSearchFilter<Object> filter = myStep.getSpeedSearchFilter();
388 if (!filter.canBeHidden(value)) return true;
389 String text = filter.getIndexedString(value);
390 return mySpeedSearch.shouldBeShowing(text);
393 public SpeedSearch getSpeedSearch() {
394 return mySpeedSearch;
398 protected void onSelectByMnemonic(Object value) {
402 protected abstract void onChildSelectedFor(Object value);
404 protected final void notifyParentOnChildSelection() {
405 if (myParent == null || myParentValue == null) return;
406 myParent.onChildSelectedFor(myParentValue);
410 private class MyComponentAdapter extends ComponentAdapter {
411 public void componentMoved(final ComponentEvent e) {
412 processParentWindowMoved();
416 public final void setFinalRunnable(Runnable runnable) {
417 if (getParent() == null) {
418 super.setFinalRunnable(runnable);
419 } else {
420 getParent().setFinalRunnable(runnable);