2 * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
3 * Use is subject to license terms.
5 package com
.intellij
.ui
.popup
;
7 import com
.intellij
.ide
.DataManager
;
8 import com
.intellij
.openapi
.actionSystem
.DataProvider
;
9 import com
.intellij
.openapi
.actionSystem
.PlatformDataKeys
;
10 import com
.intellij
.openapi
.project
.Project
;
11 import com
.intellij
.openapi
.ui
.popup
.*;
12 import com
.intellij
.openapi
.util
.Disposer
;
13 import com
.intellij
.openapi
.util
.Pair
;
14 import com
.intellij
.ui
.PopupBorder
;
15 import com
.intellij
.ui
.ScreenUtil
;
16 import com
.intellij
.ui
.popup
.list
.ListPopupImpl
;
17 import com
.intellij
.ui
.popup
.tree
.TreePopupImpl
;
18 import com
.intellij
.ui
.popup
.util
.MnemonicsSearch
;
19 import com
.intellij
.ui
.speedSearch
.ElementFilter
;
20 import com
.intellij
.ui
.speedSearch
.SpeedSearch
;
21 import org
.jetbrains
.annotations
.NonNls
;
22 import org
.jetbrains
.annotations
.NotNull
;
26 import java
.awt
.event
.*;
27 import java
.util
.Collections
;
29 public abstract class WizardPopup
extends AbstractPopup
implements ActionListener
, ElementFilter
{
31 private static final int AUTO_POPUP_DELAY
= 750;
32 private static final Dimension MAX_SIZE
= new Dimension(Integer
.MAX_VALUE
, 600);
34 protected static final int STEP_X_PADDING
= 2;
36 private final WizardPopup myParent
;
38 protected final PopupStep
<Object
> myStep
;
39 protected WizardPopup myChild
;
41 private JScrollPane myScrollPane
;
43 private final Timer myAutoSelectionTimer
= new Timer(AUTO_POPUP_DELAY
, this);
46 private MnemonicsSearch myMnemonicsSearch
;
47 private Object myParentValue
;
49 private Point myLastOwnerPoint
;
50 private Window myOwnerWindow
;
51 private MyComponentAdapter myOwnerListener
;
53 public WizardPopup(PopupStep aStep
) {
57 public WizardPopup(JBPopup aParent
, PopupStep aStep
) {
58 myParent
= (WizardPopup
) aParent
;
61 mySpeedSearch
.setEnabled(myStep
.isSpeedSearchEnabled());
63 final JComponent content
= createContent();
65 myScrollPane
= new JScrollPane(content
);
66 myScrollPane
.setVerticalScrollBarPolicy(ScrollPaneConstants
.VERTICAL_SCROLLBAR_AS_NEEDED
);
67 myScrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants
.HORIZONTAL_SCROLLBAR_NEVER
);
68 myScrollPane
.getHorizontalScrollBar().setBorder(null);
70 myScrollPane
.getActionMap().get("unitScrollLeft").setEnabled(false);
71 myScrollPane
.getActionMap().get("unitScrollRight").setEnabled(false);
73 myScrollPane
.setBorder(null);
75 final Project project
= PlatformDataKeys
.PROJECT
.getData(DataManager
.getInstance().getDataContext());
76 init(project
, myScrollPane
, getPreferredFocusableComponent(), true, true, true, true, null,
77 false, aStep
.getTitle(), null, true, null, false, null, null, null, false, null, true, false, true, null, 0f
,
78 null, true, false, new Component
[0], null, true, Collections
.<Pair
<ActionListener
, KeyStroke
>>emptyList(), null);
80 registerAction("disposeAll", KeyEvent
.VK_ESCAPE
, InputEvent
.SHIFT_MASK
, new AbstractAction() {
81 public void actionPerformed(ActionEvent e
) {
82 if (mySpeedSearch
.isHoldingFilter()) {
83 mySpeedSearch
.reset();
91 AbstractAction goBackAction
= new AbstractAction() {
92 public void actionPerformed(ActionEvent e
) {
97 registerAction("goBack3", KeyEvent
.VK_ESCAPE
, 0, goBackAction
);
99 myMnemonicsSearch
= new MnemonicsSearch(this) {
100 protected void select(Object value
) {
101 onSelectByMnemonic(value
);
109 private void disposeAll() {
110 WizardPopup root
= PopupDispatcher
.getActiveRoot();
111 disposeAllParents(null);
112 root
.getStep().canceled();
115 public void goBack() {
116 if (mySpeedSearch
.isHoldingFilter()) {
117 mySpeedSearch
.reset();
121 if (myParent
!= null) {
122 myParent
.disposeChildren();
129 protected abstract JComponent
createContent();
131 public void dispose() {
134 myAutoSelectionTimer
.stop();
136 PopupDispatcher
.unsetShowing(this);
137 PopupDispatcher
.clearRootIfNeeded(this);
140 if (myOwnerWindow
!= null && myOwnerListener
!= null) {
141 myOwnerWindow
.removeComponentListener(myOwnerListener
);
146 public void disposeChildren() {
147 if (myChild
!= null) {
148 myChild
.disposeChildren();
149 Disposer
.dispose(myChild
);
154 public void show(final Component owner
, final int aScreenX
, final int aScreenY
, final boolean considerForcedXY
) {
155 Rectangle targetBounds
= new Rectangle(new Point(aScreenX
, aScreenY
), getContent().getPreferredSize());
156 ScreenUtil
.moveRectangleToFitTheScreen(targetBounds
);
158 if (getParent() != null) {
159 if (getParent().getBounds().intersects(targetBounds
)) {
160 targetBounds
.x
= getParent().getBounds().x
- targetBounds
.width
- STEP_X_PADDING
;
164 if (getParent() == null) {
165 PopupDispatcher
.setActiveRoot(this);
168 PopupDispatcher
.setShowing(this);
171 super.show(owner
, targetBounds
.x
, targetBounds
.y
, true);
174 protected void afterShow() {
178 if (!myFocusTrackback
.isMustBeShown()) {
183 private void registerAutoMove() {
184 if (myOwner
!= null) {
185 myOwnerWindow
= SwingUtilities
.getWindowAncestor(myOwner
);
186 if (myOwnerWindow
!= null) {
187 myLastOwnerPoint
= myOwnerWindow
.getLocationOnScreen();
188 myOwnerListener
= new MyComponentAdapter();
189 myOwnerWindow
.addComponentListener(myOwnerListener
);
194 private void processParentWindowMoved() {
195 if (isDisposed()) return;
197 final Point newOwnerPoint
= myOwnerWindow
.getLocationOnScreen();
199 int deltaX
= myLastOwnerPoint
.x
- newOwnerPoint
.x
;
200 int deltaY
= myLastOwnerPoint
.y
- newOwnerPoint
.y
;
202 myLastOwnerPoint
= newOwnerPoint
;
204 final Window wnd
= SwingUtilities
.getWindowAncestor(getContent());
205 final Point current
= wnd
.getLocationOnScreen();
207 setLocation(new Point(current
.x
- deltaX
, current
.y
- deltaY
));
210 protected abstract JComponent
getPreferredFocusableComponent();
212 public void cancel(InputEvent e
) {
215 Disposer
.dispose(this);
216 getStep().canceled();
220 public boolean isCancelKeyEnabled() {
221 return super.isCancelKeyEnabled() && !mySpeedSearch
.isHoldingFilter();
224 protected void disposeAllParents(InputEvent e
) {
227 if (myParent
!= null) {
228 myParent
.disposeAllParents(null);
232 public final void registerAction(@NonNls String aActionName
, int aKeyCode
, int aModifier
, Action aAction
) {
233 getInputMap().put(KeyStroke
.getKeyStroke(aKeyCode
, aModifier
), aActionName
);
234 getActionMap().put(aActionName
, aAction
);
237 protected abstract InputMap
getInputMap();
239 protected abstract ActionMap
getActionMap();
241 protected final void setParentValue(Object parentValue
) {
242 myParentValue
= parentValue
;
246 protected MyContentPanel
createContentPanel(final boolean resizable
, final PopupBorder border
, final boolean isToDrawMacCorner
) {
247 return new MyContainer(resizable
, border
, isToDrawMacCorner
);
250 private static class MyContainer
extends MyContentPanel
implements DataProvider
{
252 private MyContainer(final boolean resizable
, final PopupBorder border
, final boolean drawMacCorner
) {
253 super(resizable
, border
, drawMacCorner
);
255 setFocusCycleRoot(true);
258 public Object
getData(String dataId
) {
262 public Dimension
getPreferredSize() {
263 return computeNotBiggerDimension(super.getPreferredSize());
266 private static Dimension
computeNotBiggerDimension(Dimension ofContent
) {
267 int resultWidth
= ofContent
.width
> MAX_SIZE
.width ? MAX_SIZE
.width
: ofContent
.width
;
268 int resultHeight
= ofContent
.height
> MAX_SIZE
.height ? MAX_SIZE
.height
: ofContent
.height
;
270 return new Dimension(resultWidth
, resultHeight
);
274 public WizardPopup
getParent() {
278 public PopupStep
getStep() {
282 public final void dispatch(KeyEvent event
) {
283 if (event
.getID() != KeyEvent
.KEY_PRESSED
&& event
.getID() != KeyEvent
.KEY_RELEASED
) {
287 if (event
.getID() == KeyEvent
.KEY_PRESSED
) {
288 final KeyStroke stroke
= KeyStroke
.getKeyStroke(event
.getKeyChar(), event
.getModifiers(), false);
289 if (proceedKeyEvent(event
, stroke
)) return;
292 if (event
.getID() == KeyEvent
.KEY_RELEASED
) {
293 final KeyStroke stroke
= KeyStroke
.getKeyStroke(event
.getKeyCode(), event
.getModifiers(), true);
294 proceedKeyEvent(event
, stroke
);
298 myMnemonicsSearch
.process(event
);
299 mySpeedSearch
.process(event
);
301 if (event
.isConsumed()) return;
305 private boolean proceedKeyEvent(KeyEvent event
, KeyStroke stroke
) {
306 if (getInputMap().get(stroke
) != null) {
307 final Action action
= getActionMap().get(getInputMap().get(stroke
));
308 if (action
!= null && action
.isEnabled()) {
309 action
.actionPerformed(new ActionEvent(getContent(), event
.getID(), "", event
.getWhen(), event
.getModifiers()));
316 protected void process(KeyEvent aEvent
) {
320 public Rectangle
getBounds() {
321 return new Rectangle(getContent().getLocationOnScreen(), getContent().getSize());
324 protected static WizardPopup
createPopup(WizardPopup parent
, PopupStep step
, Object parentValue
) {
325 if (step
instanceof ListPopupStep
) {
326 return new ListPopupImpl(parent
, (ListPopupStep
)step
, parentValue
);
328 else if (step
instanceof TreePopupStep
) {
329 return new TreePopupImpl(parent
, (TreePopupStep
)step
, parentValue
);
332 throw new IllegalArgumentException(step
.getClass().toString());
336 public final void actionPerformed(ActionEvent e
) {
337 myAutoSelectionTimer
.stop();
338 if (getStep().isAutoSelectionEnabled()) {
339 onAutoSelectionTimer();
343 protected final void restartTimer() {
344 if (!myAutoSelectionTimer
.isRunning()) {
345 myAutoSelectionTimer
.start();
348 myAutoSelectionTimer
.restart();
352 protected final void stopTimer() {
353 myAutoSelectionTimer
.stop();
356 protected void onAutoSelectionTimer() {
360 public boolean shouldBeShowing(Object value
) {
361 if (!myStep
.isSpeedSearchEnabled()) return true;
362 SpeedSearchFilter
<Object
> filter
= myStep
.getSpeedSearchFilter();
363 if (!filter
.canBeHidden(value
)) return true;
364 String text
= filter
.getIndexedString(value
);
365 return mySpeedSearch
.shouldBeShowing(text
);
368 public SpeedSearch
getSpeedSearch() {
369 return mySpeedSearch
;
373 protected void onSelectByMnemonic(Object value
) {
377 protected abstract void onChildSelectedFor(Object value
);
379 protected final void notifyParentOnChildSelection() {
380 if (myParent
== null || myParentValue
== null) return;
381 myParent
.onChildSelectedFor(myParentValue
);
385 private class MyComponentAdapter
extends ComponentAdapter
{
386 public void componentMoved(final ComponentEvent e
) {
387 processParentWindowMoved();