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
;
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
) {
70 public WizardPopup(JBPopup aParent
, PopupStep
<Object
> aStep
) {
71 myParent
= (WizardPopup
) aParent
;
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();
104 AbstractAction goBackAction
= new AbstractAction() {
105 public void actionPerformed(ActionEvent e
) {
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();
134 if (myParent
!= null) {
135 myParent
.disposeChildren();
142 protected abstract JComponent
createContent();
144 public void 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
);
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);
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() {
194 if (!myFocusTrackback
.isMustBeShown()) {
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
) {
231 Disposer
.dispose(this);
232 getStep().canceled();
236 public boolean isCancelKeyEnabled() {
237 return super.isCancelKeyEnabled() && !mySpeedSearch
.isHoldingFilter();
240 protected void disposeAllParents(InputEvent e
) {
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
;
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
);
276 setFocusCycleRoot(true);
279 public Object
getData(String dataId
) {
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() {
303 public PopupStep
getStep() {
307 public final void dispatch(KeyEvent event
) {
308 if (event
.getID() != KeyEvent
.KEY_PRESSED
&& event
.getID() != KeyEvent
.KEY_RELEASED
) {
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
);
323 myMnemonicsSearch
.process(event
);
324 mySpeedSearch
.process(event
);
326 if (event
.isConsumed()) return;
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()));
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
);
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();
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
);
420 getParent().setFinalRunnable(runnable
);