From 526c5ff8aaa3062272104c06a7c2c38d6a5b65cd Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Wed, 4 Jul 2012 16:25:01 +0400 Subject: [PATCH] IDEA-66333 Quick documentation lookup on mouse hover 1. 'Auto quick doc' is closed on pressing any key; 2. Don't show auto quick doc if any modifier key is set (e.g. prevent it from popping out when we want to navigate via Ctrl+click or evaluate via Alt+click); --- .../documentation/DockablePopupManager.java | 4 +- .../documentation/DocumentationManager.java | 55 +- .../QuickDocOnMouseOverManager.java | 10 +- .../ui/popup/ComponentPopupBuilder.java | 297 +++--- .../intellij/openapi/ui/popup/JBPopup.java | 14 +- .../ui/popup/ComponentPopupBuilderImpl.java | 658 ++++++------- .../com/intellij/ui/popup/WizardPopup.java | 898 +++++++++--------- 7 files changed, 1009 insertions(+), 927 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DockablePopupManager.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DockablePopupManager.java index 7bbe8596b929..d860400f9948 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DockablePopupManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DockablePopupManager.java @@ -49,9 +49,9 @@ public abstract class DockablePopupManager { protected ToolWindow myToolWindow = null; protected boolean myAutoUpdateDocumentation = PropertiesComponent.getInstance().isTrueValue(getAutoUpdateEnabledProperty()); protected Runnable myAutoUpdateRequest; - protected final Project myProject; + @NotNull protected final Project myProject; - public DockablePopupManager(Project project) { + public DockablePopupManager(@NotNull Project project) { myProject = project; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java index fa7dbdd11e07..f54558ea745c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java @@ -61,6 +61,7 @@ import com.intellij.ui.popup.NotLookupOrSearchCondition; import com.intellij.ui.popup.PopupPositionManager; import com.intellij.ui.popup.PopupUpdateProcessor; import com.intellij.util.Alarm; +import com.intellij.util.BooleanFunction; import com.intellij.util.Consumer; import com.intellij.util.Processor; import org.jetbrains.annotations.NonNls; @@ -71,6 +72,7 @@ import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; import java.lang.ref.WeakReference; import java.util.*; import java.util.List; @@ -94,6 +96,7 @@ public class DocumentationManager extends DockablePopupManager'element' argument is a {@code Runnable} descriptor + * @param closeCallback callback to be notified on target hint close (if any) + * @param closeOnSneeze flag that defines whether quick doc control should be as non-obtrusive as possible. E.g. there are at least + * two possible situations - the quick doc is shown automatically on mouse over element; the quick doc is shown + * on explicit action call (Ctrl+Q). We want to close the doc on, say, editor viewport position change + * at the first situation but don't want to do that at the second + */ public void showJavaDocInfo(@NotNull Editor editor, @NotNull final PsiElement element, @NotNull final PsiElement original, - @Nullable Runnable closeCallback) + @Nullable Runnable closeCallback, + boolean closeOnSneeze) { myEditor = editor; + myCloseOnSneeze = closeOnSneeze; showJavaDocInfo(element, original, closeCallback); } @@ -360,6 +393,7 @@ public class DocumentationManager extends DockablePopupManager() { public Boolean compute() { + myCloseOnSneeze = false; if (closeCallback != null) { closeCallback.run(); } @@ -374,6 +408,15 @@ public class DocumentationManager extends DockablePopupManager() { + @Override + public boolean fun(KeyEvent event) { + if (myCloseOnSneeze) { + closeDocHint(); + } + return false; + } + }) .createPopup(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocOnMouseOverManager.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocOnMouseOverManager.java index 6fc57fc1ac2d..edfef729531f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocOnMouseOverManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocOnMouseOverManager.java @@ -103,6 +103,12 @@ public class QuickDocOnMouseOverManager { closeAutoQuickDocComponentIfNecessary(); return; } + + if (e.getMouseEvent().getModifiers() != 0) { + // Don't show the control when any modifier is active (e.g. Ctrl or Alt is hold). There is a common situation that a user + // wants to navigate via Ctrl+click or perform quick evaluate by Alt+click. + return; + } Editor editor = e.getEditor(); Project project = editor.getProject(); @@ -218,7 +224,7 @@ public class QuickDocOnMouseOverManager { return; } - if (myHintManager.hasShownHintsThatWillHideByOtherHint(true)) { + if (!info.docManager.hasDockedDocWindow() && myHintManager.hasShownHintsThatWillHideByOtherHint(false)) { // We don't want to show a quick doc control if there is an active hint (e.g. the mouse is under an invalid element // and corresponding error info is shown). myAlarm.addRequest(this, EditorSettingsExternalizable.getInstance().getQuickDocOnMouseOverElementDelayMillis()); @@ -228,7 +234,7 @@ public class QuickDocOnMouseOverManager { info.editor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION, info.editor.offsetToVisualPosition(info.originalElement.getTextRange().getStartOffset())); try { - info.docManager.showJavaDocInfo(info.editor, info.targetElement, info.originalElement, myHintCloseCallback); + info.docManager.showJavaDocInfo(info.editor, info.targetElement, info.originalElement, myHintCloseCallback, true); myDocumentationManager = new WeakReference(info.docManager); } finally { diff --git a/platform/platform-api/src/com/intellij/openapi/ui/popup/ComponentPopupBuilder.java b/platform/platform-api/src/com/intellij/openapi/ui/popup/ComponentPopupBuilder.java index 87a836c38a45..b0733c86a257 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/popup/ComponentPopupBuilder.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/popup/ComponentPopupBuilder.java @@ -1,143 +1,154 @@ -/* - * Copyright 2000-2010 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.intellij.openapi.ui.popup; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Pair; -import com.intellij.ui.InplaceButton; -import com.intellij.util.Processor; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionListener; -import java.util.List; - -/** - * @author max - */ -public interface ComponentPopupBuilder { - - @NotNull - ComponentPopupBuilder setTitle(String title); - - @NotNull - ComponentPopupBuilder setResizable(final boolean forceResizable); - - @NotNull - ComponentPopupBuilder setMovable(final boolean forceMovable); - - @NotNull - ComponentPopupBuilder setRequestFocus(boolean requestFocus); - - @NotNull - ComponentPopupBuilder setFocusable(boolean focusable); - - @NotNull - ComponentPopupBuilder setRequestFocusCondition(Project project, Condition condition); - - /** - * @deprecated all popups are heavyweight now, lightweight popups no linger supported - */ - @NotNull - ComponentPopupBuilder setForceHeavyweight(boolean forceHeavyweight); - - /** - * @see com.intellij.openapi.util.DimensionService - */ - @NotNull - ComponentPopupBuilder setDimensionServiceKey(@Nullable final Project project, @NonNls final String dimensionServiceKey, final boolean useForXYLocation); - - @NotNull - ComponentPopupBuilder setCancelCallback(final Computable shouldProceed); - - @NotNull - ComponentPopupBuilder setCancelOnClickOutside(boolean cancel); - - @NotNull - ComponentPopupBuilder addListener(JBPopupListener listener); - - @NotNull - ComponentPopupBuilder setCancelOnMouseOutCallback(final MouseChecker shouldCancel); - - @NotNull - JBPopup createPopup(); - - @NotNull - ComponentPopupBuilder setCancelButton(@NotNull IconButton cancelButton); - - @NotNull - ComponentPopupBuilder setCancelOnOtherWindowOpen(boolean cancelOnWindow); - - @NotNull ComponentPopupBuilder setTitleIcon(@NotNull ActiveIcon icon); - - @NotNull ComponentPopupBuilder setCancelKeyEnabled(boolean enabled); - - @NotNull ComponentPopupBuilder setLocateByContent(boolean byContent); - - @NotNull ComponentPopupBuilder setLocateWithinScreenBounds(boolean within); - - @NotNull ComponentPopupBuilder setMinSize(Dimension minSize); - - @NotNull ComponentPopupBuilder setAlpha(float alpha); - - @NotNull ComponentPopupBuilder setBelongsToGlobalPopupStack(boolean isInStack); - - @NotNull ComponentPopupBuilder setProject(Project project); - - @NotNull ComponentPopupBuilder addUserData(Object object); - - @NotNull ComponentPopupBuilder setModalContext(boolean modal); - - @NotNull - ComponentPopupBuilder setFocusOwners(@NotNull Component[] focusOwners); - - /** - * Adds "advertising" text to the bottom (e.g.: hints in code completion popup). - * - * @param text Text to set. - * @return This. - */ - @NotNull - ComponentPopupBuilder setAdText(@Nullable String text); - - @NotNull - ComponentPopupBuilder setAdText(@Nullable String text, int textAlignment); - - @NotNull ComponentPopupBuilder setShowShadow(boolean show); - - @NotNull - ComponentPopupBuilder setCommandButton(@NotNull InplaceButton commandButton); - - @NotNull - ComponentPopupBuilder setCouldPin(@Nullable Processor callback); - - @NotNull - ComponentPopupBuilder setKeyboardActions(@NotNull List> keyboardActions); - - @NotNull - ComponentPopupBuilder setSettingButtons(@NotNull Component button); - - @NotNull - ComponentPopupBuilder setMayBeParent(boolean mayBeParent); - - ComponentPopupBuilder setCancelOnWindowDeactivation(boolean cancelOnWindowDeactivation); -} +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.openapi.ui.popup; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Pair; +import com.intellij.ui.InplaceButton; +import com.intellij.util.BooleanFunction; +import com.intellij.util.Processor; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.List; + +/** + * @author max + */ +public interface ComponentPopupBuilder { + + @NotNull + ComponentPopupBuilder setTitle(String title); + + @NotNull + ComponentPopupBuilder setResizable(final boolean forceResizable); + + @NotNull + ComponentPopupBuilder setMovable(final boolean forceMovable); + + @NotNull + ComponentPopupBuilder setRequestFocus(boolean requestFocus); + + @NotNull + ComponentPopupBuilder setFocusable(boolean focusable); + + @NotNull + ComponentPopupBuilder setRequestFocusCondition(Project project, Condition condition); + + /** + * @deprecated all popups are heavyweight now, lightweight popups no linger supported + */ + @NotNull + ComponentPopupBuilder setForceHeavyweight(boolean forceHeavyweight); + + /** + * @see com.intellij.openapi.util.DimensionService + */ + @NotNull + ComponentPopupBuilder setDimensionServiceKey(@Nullable final Project project, @NonNls final String dimensionServiceKey, final boolean useForXYLocation); + + @NotNull + ComponentPopupBuilder setCancelCallback(final Computable shouldProceed); + + @NotNull + ComponentPopupBuilder setCancelOnClickOutside(boolean cancel); + + @NotNull + ComponentPopupBuilder addListener(JBPopupListener listener); + + @NotNull + ComponentPopupBuilder setCancelOnMouseOutCallback(final MouseChecker shouldCancel); + + @NotNull + JBPopup createPopup(); + + @NotNull + ComponentPopupBuilder setCancelButton(@NotNull IconButton cancelButton); + + @NotNull + ComponentPopupBuilder setCancelOnOtherWindowOpen(boolean cancelOnWindow); + + @NotNull ComponentPopupBuilder setTitleIcon(@NotNull ActiveIcon icon); + + @NotNull ComponentPopupBuilder setCancelKeyEnabled(boolean enabled); + + @NotNull ComponentPopupBuilder setLocateByContent(boolean byContent); + + @NotNull ComponentPopupBuilder setLocateWithinScreenBounds(boolean within); + + @NotNull ComponentPopupBuilder setMinSize(Dimension minSize); + + @NotNull ComponentPopupBuilder setAlpha(float alpha); + + @NotNull ComponentPopupBuilder setBelongsToGlobalPopupStack(boolean isInStack); + + @NotNull ComponentPopupBuilder setProject(Project project); + + @NotNull ComponentPopupBuilder addUserData(Object object); + + @NotNull ComponentPopupBuilder setModalContext(boolean modal); + + @NotNull + ComponentPopupBuilder setFocusOwners(@NotNull Component[] focusOwners); + + /** + * Adds "advertising" text to the bottom (e.g.: hints in code completion popup). + * + * @param text Text to set. + * @return This. + */ + @NotNull + ComponentPopupBuilder setAdText(@Nullable String text); + + @NotNull + ComponentPopupBuilder setAdText(@Nullable String text, int textAlignment); + + @NotNull ComponentPopupBuilder setShowShadow(boolean show); + + @NotNull + ComponentPopupBuilder setCommandButton(@NotNull InplaceButton commandButton); + + @NotNull + ComponentPopupBuilder setCouldPin(@Nullable Processor callback); + + @NotNull + ComponentPopupBuilder setKeyboardActions(@NotNull List> keyboardActions); + + @NotNull + ComponentPopupBuilder setSettingButtons(@NotNull Component button); + + @NotNull + ComponentPopupBuilder setMayBeParent(boolean mayBeParent); + + ComponentPopupBuilder setCancelOnWindowDeactivation(boolean cancelOnWindowDeactivation); + + /** + * Allows to define custom strategy for processing {@link JBPopup#dispatchKeyEvent(KeyEvent)} + * + * @param handler strategy to use + * @return this + */ + @NotNull + ComponentPopupBuilder setKeyEventHandler(@NotNull BooleanFunction handler); +} diff --git a/platform/platform-api/src/com/intellij/openapi/ui/popup/JBPopup.java b/platform/platform-api/src/com/intellij/openapi/ui/popup/JBPopup.java index 85d8cc1068ee..0215cb2c1de6 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/popup/JBPopup.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/popup/JBPopup.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; /** * Base interface for popup windows. @@ -190,4 +191,15 @@ public interface JBPopup extends Disposable, LightweightWindow { void setAdText(String s, @JdkConstants.HorizontalAlignment int alignment); void setDataProvider(@NotNull DataProvider dataProvider); + + /** + * This callback is called when new key event from the event queue is being processed. + *

+ * The popup has a right to decide if its further processing should be continued (method return value). + * + * @param e new key event being processed + * @return true if the event is completely dispatched, i.e. no further processing is necessary; + * false otherwise + */ + boolean dispatchKeyEvent(@NotNull KeyEvent e); } diff --git a/platform/platform-impl/src/com/intellij/ui/popup/ComponentPopupBuilderImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/ComponentPopupBuilderImpl.java index 472f1626a18e..11356428436e 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/ComponentPopupBuilderImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/ComponentPopupBuilderImpl.java @@ -1,324 +1,334 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.intellij.ui.popup; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.*; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Pair; -import com.intellij.ui.InplaceButton; -import com.intellij.util.Processor; -import com.intellij.util.ui.EmptyIcon; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionListener; -import java.util.*; -import java.util.List; - -/** - * User: anna - * Date: 15-Mar-2006 - */ -public class ComponentPopupBuilderImpl implements ComponentPopupBuilder { - private String myTitle = ""; - private boolean myResizable; - private boolean myMovable; - private final JComponent myComponent; - private final JComponent myPreferredFocusedComponent; - private boolean myRequestFocus; - private boolean myForceHeavyweight; - private String myDimensionServiceKey = null; - private Computable myCallback = null; - private Project myProject; - private boolean myCancelOnClickOutside = true; - private boolean myCancelOnWindowDeactivation = true; - private final Set myListeners = new LinkedHashSet(); - private boolean myUseDimServiceForXYLocation; - - private IconButton myCancelButton; - private MouseChecker myCancelOnMouseOutCallback; - private boolean myCancelOnWindow; - private ActiveIcon myTitleIcon = new ActiveIcon(EmptyIcon.ICON_0); - private boolean myCancelKeyEnabled = true; - private boolean myLocateByContent = false; - private boolean myPlacewithinScreen = true; - private Processor myPinCallback = null; - private Dimension myMinSize; - private MaskProvider myMaskProvider; - private float myAlpha; - private ArrayList myUserData; - - private boolean myInStack = true; - private boolean myModalContext = true; - private Component[] myFocusOwners = new Component[0]; - - private String myAd; - private boolean myShowShadow = true; - private boolean myFocusable = true; - private boolean myHeaderAlwaysFocusable; - private InplaceButton myCommandButton; - private List> myKeyboardActions = Collections.emptyList(); - private Component mySettingsButtons; - private boolean myMayBeParent; - private int myAdAlignment = SwingUtilities.LEFT; - - public ComponentPopupBuilderImpl(final JComponent component, - final JComponent preferredFocusedComponent) { - myComponent = component; - myPreferredFocusedComponent = preferredFocusedComponent; - } - - @NotNull - public ComponentPopupBuilder setMayBeParent(boolean mayBeParent) { - myMayBeParent = mayBeParent; - return this; - } - - @NotNull - public ComponentPopupBuilder setTitle(String title) { - myTitle = title; - return this; - } - - @NotNull - public ComponentPopupBuilder setResizable(final boolean resizable) { - myResizable = resizable; - return this; - } - - @NotNull - public ComponentPopupBuilder setMovable(final boolean movable) { - myMovable = movable; - return this; - } - - @NotNull - public ComponentPopupBuilder setCancelOnClickOutside(final boolean cancel) { - myCancelOnClickOutside = cancel; - return this; - } - - @NotNull - public ComponentPopupBuilder setCancelOnMouseOutCallback(final MouseChecker shouldCancel) { - myCancelOnMouseOutCallback = shouldCancel; - return this; - } - - @NotNull - public ComponentPopupBuilder addListener(final JBPopupListener listener) { - myListeners.add(listener); - return this; - } - - @NotNull - public ComponentPopupBuilder setRequestFocus(final boolean requestFocus) { - myRequestFocus = requestFocus; - return this; - } - - @NotNull - public ComponentPopupBuilder setFocusable(final boolean focusable) { - myFocusable = focusable; - return this; - } - - @NotNull - public ComponentPopupBuilder setForceHeavyweight(final boolean forceHeavyweight) { - myForceHeavyweight = forceHeavyweight; - return this; - } - - @NotNull - public ComponentPopupBuilder setDimensionServiceKey(final Project project, final String dimensionServiceKey, final boolean useForXYLocation) { - myDimensionServiceKey = dimensionServiceKey; - myUseDimServiceForXYLocation = useForXYLocation; - myProject = project; - return this; - } - - @NotNull - public ComponentPopupBuilder setCancelCallback(final Computable shouldProceed) { - myCallback = shouldProceed; - return this; - } - - @NotNull - public ComponentPopupBuilder setCancelButton(@NotNull final IconButton cancelButton) { - myCancelButton = cancelButton; - return this; - } - @NotNull - public ComponentPopupBuilder setCommandButton(@NotNull InplaceButton button) { - myCommandButton = button; - return this; - } - - @NotNull - public ComponentPopupBuilder setCouldPin(@Nullable final Processor callback) { - myPinCallback = callback; - return this; - } - - @NotNull - public ComponentPopupBuilder setKeyboardActions(@NotNull List> keyboardActions) { - myKeyboardActions = keyboardActions; - return this; - } - - @NotNull - public ComponentPopupBuilder setSettingButtons(@NotNull Component button) { - mySettingsButtons = button; - return this; - } - - @NotNull - public ComponentPopupBuilder setCancelOnOtherWindowOpen(final boolean cancelOnWindow) { - myCancelOnWindow = cancelOnWindow; - return this; - } - - @Override - public ComponentPopupBuilder setCancelOnWindowDeactivation(boolean cancelOnWindowDeactivation) { - myCancelOnWindowDeactivation = cancelOnWindowDeactivation; - return this; - } - - @NotNull - public ComponentPopupBuilder setProject(Project project) { - myProject = project; - return this; - } - - @NotNull - public JBPopup createPopup() { - final AbstractPopup popup = new AbstractPopup().init(myProject, myComponent, myPreferredFocusedComponent, myRequestFocus, myFocusable, myForceHeavyweight, - myMovable, myDimensionServiceKey, myResizable, myTitle, - myCallback, myCancelOnClickOutside, myListeners, myUseDimServiceForXYLocation, myCommandButton, - myCancelButton, - myCancelOnMouseOutCallback, myCancelOnWindow, myTitleIcon, myCancelKeyEnabled, myLocateByContent, - myPlacewithinScreen, myMinSize, myAlpha, myMaskProvider, myInStack, myModalContext, myFocusOwners, myAd, myAdAlignment, - myHeaderAlwaysFocusable, myKeyboardActions, mySettingsButtons, myPinCallback, myMayBeParent, - myShowShadow, myCancelOnWindowDeactivation); - if (myUserData != null) { - popup.setUserData(myUserData); - } - //default disposable parent - Disposer.register(ApplicationManager.getApplication(), popup); - return popup; - } - - @NotNull - public ComponentPopupBuilder setRequestFocusCondition(Project project, Condition condition) { - myRequestFocus = condition.value(project); - return this; - } - - @NotNull - public ComponentPopupBuilder setTitleIcon(@NotNull final ActiveIcon icon) { - myTitleIcon = icon; - return this; - } - - @NotNull - public ComponentPopupBuilder setCancelKeyEnabled(final boolean enabled) { - myCancelKeyEnabled = enabled; - return this; - } - - @NotNull - public ComponentPopupBuilder setLocateByContent(final boolean byContent) { - myLocateByContent = byContent; - return this; - } - - @NotNull - public ComponentPopupBuilder setLocateWithinScreenBounds(final boolean within) { - myPlacewithinScreen = within; - return this; - } - - @NotNull - public ComponentPopupBuilder setMinSize(final Dimension minSize) { - myMinSize = minSize; - return this; - } - - @NotNull - public ComponentPopupBuilder setMaskProvider(MaskProvider maskProvider) { - myMaskProvider = maskProvider; - return this; - } - - @NotNull - public ComponentPopupBuilder setAlpha(final float alpha) { - myAlpha = alpha; - return this; - } - - @NotNull - public ComponentPopupBuilder setBelongsToGlobalPopupStack(final boolean isInStack) { - myInStack = isInStack; - return this; - } - - @NotNull - public ComponentPopupBuilder addUserData(final Object object) { - if (myUserData == null) { - myUserData = new ArrayList(); - } - myUserData.add(object); - return this; - } - - @NotNull - public ComponentPopupBuilder setModalContext(final boolean modal) { - myModalContext = modal; - return this; - } - - @NotNull - public ComponentPopupBuilder setFocusOwners(@NotNull final Component[] focusOwners) { - myFocusOwners = focusOwners; - return this; - } - - @NotNull - public ComponentPopupBuilder setAdText(@Nullable final String text) { - return setAdText(text, SwingUtilities.LEFT); - } - - @NotNull - @Override - public ComponentPopupBuilder setAdText(@Nullable String text, int textAlignment) { - myAd = text; - myAdAlignment = textAlignment; - return this; - } - - @NotNull - @Override - public ComponentPopupBuilder setShowShadow(boolean show) { - myShowShadow = show; - return this; - } -} +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.ui.popup; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.*; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; +import com.intellij.ui.InplaceButton; +import com.intellij.util.BooleanFunction; +import com.intellij.util.Processor; +import com.intellij.util.ui.EmptyIcon; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.*; +import java.util.List; + +/** + * User: anna + * Date: 15-Mar-2006 + */ +public class ComponentPopupBuilderImpl implements ComponentPopupBuilder { + private String myTitle = ""; + private boolean myResizable; + private boolean myMovable; + private final JComponent myComponent; + private final JComponent myPreferredFocusedComponent; + private boolean myRequestFocus; + private boolean myForceHeavyweight; + private String myDimensionServiceKey = null; + private Computable myCallback = null; + private Project myProject; + private boolean myCancelOnClickOutside = true; + private boolean myCancelOnWindowDeactivation = true; + private final Set myListeners = new LinkedHashSet(); + private boolean myUseDimServiceForXYLocation; + + private IconButton myCancelButton; + private MouseChecker myCancelOnMouseOutCallback; + private boolean myCancelOnWindow; + private ActiveIcon myTitleIcon = new ActiveIcon(EmptyIcon.ICON_0); + private boolean myCancelKeyEnabled = true; + private boolean myLocateByContent = false; + private boolean myPlacewithinScreen = true; + private Processor myPinCallback = null; + private Dimension myMinSize; + private MaskProvider myMaskProvider; + private float myAlpha; + private ArrayList myUserData; + + private boolean myInStack = true; + private boolean myModalContext = true; + private Component[] myFocusOwners = new Component[0]; + + private String myAd; + private boolean myShowShadow = true; + private boolean myFocusable = true; + private boolean myHeaderAlwaysFocusable; + private InplaceButton myCommandButton; + private List> myKeyboardActions = Collections.emptyList(); + private Component mySettingsButtons; + private boolean myMayBeParent; + private int myAdAlignment = SwingUtilities.LEFT; + private BooleanFunction myKeyEventHandler; + + public ComponentPopupBuilderImpl(final JComponent component, + final JComponent preferredFocusedComponent) { + myComponent = component; + myPreferredFocusedComponent = preferredFocusedComponent; + } + + @NotNull + public ComponentPopupBuilder setMayBeParent(boolean mayBeParent) { + myMayBeParent = mayBeParent; + return this; + } + + @NotNull + public ComponentPopupBuilder setTitle(String title) { + myTitle = title; + return this; + } + + @NotNull + public ComponentPopupBuilder setResizable(final boolean resizable) { + myResizable = resizable; + return this; + } + + @NotNull + public ComponentPopupBuilder setMovable(final boolean movable) { + myMovable = movable; + return this; + } + + @NotNull + public ComponentPopupBuilder setCancelOnClickOutside(final boolean cancel) { + myCancelOnClickOutside = cancel; + return this; + } + + @NotNull + public ComponentPopupBuilder setCancelOnMouseOutCallback(final MouseChecker shouldCancel) { + myCancelOnMouseOutCallback = shouldCancel; + return this; + } + + @NotNull + public ComponentPopupBuilder addListener(final JBPopupListener listener) { + myListeners.add(listener); + return this; + } + + @NotNull + public ComponentPopupBuilder setRequestFocus(final boolean requestFocus) { + myRequestFocus = requestFocus; + return this; + } + + @NotNull + public ComponentPopupBuilder setFocusable(final boolean focusable) { + myFocusable = focusable; + return this; + } + + @NotNull + public ComponentPopupBuilder setForceHeavyweight(final boolean forceHeavyweight) { + myForceHeavyweight = forceHeavyweight; + return this; + } + + @NotNull + public ComponentPopupBuilder setDimensionServiceKey(final Project project, final String dimensionServiceKey, final boolean useForXYLocation) { + myDimensionServiceKey = dimensionServiceKey; + myUseDimServiceForXYLocation = useForXYLocation; + myProject = project; + return this; + } + + @NotNull + public ComponentPopupBuilder setCancelCallback(final Computable shouldProceed) { + myCallback = shouldProceed; + return this; + } + + @NotNull + public ComponentPopupBuilder setCancelButton(@NotNull final IconButton cancelButton) { + myCancelButton = cancelButton; + return this; + } + @NotNull + public ComponentPopupBuilder setCommandButton(@NotNull InplaceButton button) { + myCommandButton = button; + return this; + } + + @NotNull + public ComponentPopupBuilder setCouldPin(@Nullable final Processor callback) { + myPinCallback = callback; + return this; + } + + @NotNull + public ComponentPopupBuilder setKeyboardActions(@NotNull List> keyboardActions) { + myKeyboardActions = keyboardActions; + return this; + } + + @NotNull + public ComponentPopupBuilder setSettingButtons(@NotNull Component button) { + mySettingsButtons = button; + return this; + } + + @NotNull + public ComponentPopupBuilder setCancelOnOtherWindowOpen(final boolean cancelOnWindow) { + myCancelOnWindow = cancelOnWindow; + return this; + } + + @Override + public ComponentPopupBuilder setCancelOnWindowDeactivation(boolean cancelOnWindowDeactivation) { + myCancelOnWindowDeactivation = cancelOnWindowDeactivation; + return this; + } + + @NotNull + @Override + public ComponentPopupBuilder setKeyEventHandler(@NotNull BooleanFunction handler) { + myKeyEventHandler = handler; + return this; + } + + @NotNull + public ComponentPopupBuilder setProject(Project project) { + myProject = project; + return this; + } + + @NotNull + public JBPopup createPopup() { + final AbstractPopup popup = new AbstractPopup().init(myProject, myComponent, myPreferredFocusedComponent, myRequestFocus, myFocusable, myForceHeavyweight, + myMovable, myDimensionServiceKey, myResizable, myTitle, + myCallback, myCancelOnClickOutside, myListeners, myUseDimServiceForXYLocation, myCommandButton, + myCancelButton, + myCancelOnMouseOutCallback, myCancelOnWindow, myTitleIcon, myCancelKeyEnabled, myLocateByContent, + myPlacewithinScreen, myMinSize, myAlpha, myMaskProvider, myInStack, myModalContext, myFocusOwners, myAd, myAdAlignment, + myHeaderAlwaysFocusable, myKeyboardActions, mySettingsButtons, myPinCallback, myMayBeParent, + myShowShadow, myCancelOnWindowDeactivation, myKeyEventHandler); + if (myUserData != null) { + popup.setUserData(myUserData); + } + //default disposable parent + Disposer.register(ApplicationManager.getApplication(), popup); + return popup; + } + + @NotNull + public ComponentPopupBuilder setRequestFocusCondition(Project project, Condition condition) { + myRequestFocus = condition.value(project); + return this; + } + + @NotNull + public ComponentPopupBuilder setTitleIcon(@NotNull final ActiveIcon icon) { + myTitleIcon = icon; + return this; + } + + @NotNull + public ComponentPopupBuilder setCancelKeyEnabled(final boolean enabled) { + myCancelKeyEnabled = enabled; + return this; + } + + @NotNull + public ComponentPopupBuilder setLocateByContent(final boolean byContent) { + myLocateByContent = byContent; + return this; + } + + @NotNull + public ComponentPopupBuilder setLocateWithinScreenBounds(final boolean within) { + myPlacewithinScreen = within; + return this; + } + + @NotNull + public ComponentPopupBuilder setMinSize(final Dimension minSize) { + myMinSize = minSize; + return this; + } + + @NotNull + public ComponentPopupBuilder setMaskProvider(MaskProvider maskProvider) { + myMaskProvider = maskProvider; + return this; + } + + @NotNull + public ComponentPopupBuilder setAlpha(final float alpha) { + myAlpha = alpha; + return this; + } + + @NotNull + public ComponentPopupBuilder setBelongsToGlobalPopupStack(final boolean isInStack) { + myInStack = isInStack; + return this; + } + + @NotNull + public ComponentPopupBuilder addUserData(final Object object) { + if (myUserData == null) { + myUserData = new ArrayList(); + } + myUserData.add(object); + return this; + } + + @NotNull + public ComponentPopupBuilder setModalContext(final boolean modal) { + myModalContext = modal; + return this; + } + + @NotNull + public ComponentPopupBuilder setFocusOwners(@NotNull final Component[] focusOwners) { + myFocusOwners = focusOwners; + return this; + } + + @NotNull + public ComponentPopupBuilder setAdText(@Nullable final String text) { + return setAdText(text, SwingUtilities.LEFT); + } + + @NotNull + @Override + public ComponentPopupBuilder setAdText(@Nullable String text, int textAlignment) { + myAd = text; + myAdAlignment = textAlignment; + return this; + } + + @NotNull + @Override + public ComponentPopupBuilder setShowShadow(boolean show) { + myShowShadow = show; + return this; + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java b/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java index 5274cbdb9663..a04deacf9396 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java @@ -1,449 +1,449 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.ui.popup; - -import com.intellij.ide.DataManager; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.*; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Pair; -import com.intellij.ui.PopupBorder; -import com.intellij.ui.ScreenUtil; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.popup.list.ListPopupImpl; -import com.intellij.ui.popup.tree.TreePopupImpl; -import com.intellij.ui.popup.util.MnemonicsSearch; -import com.intellij.ui.speedSearch.ElementFilter; -import com.intellij.ui.speedSearch.SpeedSearch; -import com.intellij.util.ui.UIUtil; -import org.intellij.lang.annotations.JdkConstants; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import java.util.Collections; - -public abstract class WizardPopup extends AbstractPopup implements ActionListener, ElementFilter { - private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.WizardPopup"); - - private static final int AUTO_POPUP_DELAY = 750; - private static final Dimension MAX_SIZE = new Dimension(Integer.MAX_VALUE, 600); - - protected static final int STEP_X_PADDING = 2; - - private final WizardPopup myParent; - - protected final PopupStep myStep; - protected WizardPopup myChild; - - private final Timer myAutoSelectionTimer = UIUtil.createNamedTimer("Wizard autoselection",AUTO_POPUP_DELAY, this); - - private MnemonicsSearch myMnemonicsSearch; - private Object myParentValue; - - private Point myLastOwnerPoint; - private Window myOwnerWindow; - private MyComponentAdapter myOwnerListener; - - private final ActionMap myActionMap = new ActionMap(); - private final InputMap myInputMap = new InputMap(); - - public WizardPopup(PopupStep aStep) { - this(null, aStep); - } - - public WizardPopup(JBPopup aParent, PopupStep aStep) { - myParent = (WizardPopup) aParent; - myStep = aStep; - - mySpeedSearch.setEnabled(myStep.isSpeedSearchEnabled()); - - final JComponent content = createContent(); - - JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(content); - scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - scrollPane.getHorizontalScrollBar().setBorder(null); - - scrollPane.getActionMap().get("unitScrollLeft").setEnabled(false); - scrollPane.getActionMap().get("unitScrollRight").setEnabled(false); - - scrollPane.setBorder(null); - - final Project project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext()); - init(project, scrollPane, getPreferredFocusableComponent(), true, true, true, true, null, - false, aStep.getTitle(), null, true, null, false, null, null, null, false, null, true, false, true, null, 0f, - null, true, false, new Component[0], null, SwingUtilities.LEFT, true, Collections.>emptyList(), null, null, false, true, - true); - - registerAction("disposeAll", KeyEvent.VK_ESCAPE, InputEvent.SHIFT_MASK, new AbstractAction() { - public void actionPerformed(ActionEvent e) { - if (mySpeedSearch.isHoldingFilter()) { - mySpeedSearch.reset(); - } - else { - disposeAll(); - } - } - }); - - AbstractAction goBackAction = new AbstractAction() { - public void actionPerformed(ActionEvent e) { - goBack(); - } - }; - - registerAction("goBack3", KeyEvent.VK_ESCAPE, 0, goBackAction); - - myMnemonicsSearch = new MnemonicsSearch(this) { - protected void select(Object value) { - onSelectByMnemonic(value); - } - }; - - - - } - - private void disposeAll() { - WizardPopup root = PopupDispatcher.getActiveRoot(); - disposeAllParents(null); - root.getStep().canceled(); - } - - public void goBack() { - if (mySpeedSearch.isHoldingFilter()) { - mySpeedSearch.reset(); - return; - } - - if (myParent != null) { - myParent.disposeChildren(); - } - else { - disposeAll(); - } - } - - protected abstract JComponent createContent(); - - public void dispose() { - super.dispose(); - - myAutoSelectionTimer.stop(); - - PopupDispatcher.unsetShowing(this); - PopupDispatcher.clearRootIfNeeded(this); - - - if (myOwnerWindow != null && myOwnerListener != null) { - myOwnerWindow.removeComponentListener(myOwnerListener); - } - } - - - public void disposeChildren() { - if (myChild != null) { - myChild.disposeChildren(); - Disposer.dispose(myChild); - myChild = null; - } - } - - public void show(final Component owner, final int aScreenX, final int aScreenY, final boolean considerForcedXY) { - LOG.assertTrue (!isDisposed()); - - Rectangle targetBounds = new Rectangle(new Point(aScreenX, aScreenY), getContent().getPreferredSize()); - ScreenUtil.moveRectangleToFitTheScreen(targetBounds); - - if (getParent() != null) { - final Rectangle parentBounds = getParent().getBounds(); - parentBounds.x += STEP_X_PADDING; - parentBounds.width -= STEP_X_PADDING * 2; - if (parentBounds.intersects(targetBounds)) { - targetBounds.x = getParent().getBounds().x - targetBounds.width - STEP_X_PADDING; - } - } - - if (getParent() == null) { - PopupDispatcher.setActiveRoot(this); - } - else { - PopupDispatcher.setShowing(this); - } - - LOG.assertTrue (!isDisposed(), "Disposed popup, parent="+getParent()); - super.show(owner, targetBounds.x, targetBounds.y, true); - } - - protected void afterShow() { - super.afterShow(); - registerAutoMove(); - - if (!myFocusTrackback.isMustBeShown()) { - cancel(); - } - } - - private void registerAutoMove() { - if (myOwner != null) { - myOwnerWindow = SwingUtilities.getWindowAncestor(myOwner); - if (myOwnerWindow != null) { - myLastOwnerPoint = myOwnerWindow.getLocationOnScreen(); - myOwnerListener = new MyComponentAdapter(); - myOwnerWindow.addComponentListener(myOwnerListener); - } - } - } - - private void processParentWindowMoved() { - if (isDisposed()) return; - - final Point newOwnerPoint = myOwnerWindow.getLocationOnScreen(); - - int deltaX = myLastOwnerPoint.x - newOwnerPoint.x; - int deltaY = myLastOwnerPoint.y - newOwnerPoint.y; - - myLastOwnerPoint = newOwnerPoint; - - final Window wnd = SwingUtilities.getWindowAncestor(getContent()); - final Point current = wnd.getLocationOnScreen(); - - setLocation(new Point(current.x - deltaX, current.y - deltaY)); - } - - protected abstract JComponent getPreferredFocusableComponent(); - - public void cancel(InputEvent e) { - super.cancel(e); - disposeChildren(); - Disposer.dispose(this); - getStep().canceled(); - } - - @Override - public boolean isCancelKeyEnabled() { - return super.isCancelKeyEnabled() && !mySpeedSearch.isHoldingFilter(); - } - - protected void disposeAllParents(InputEvent e) { - myDisposeEvent = e; - dispose(); - if (myParent != null) { - myParent.disposeAllParents(null); - } - } - - public final void registerAction(@NonNls String aActionName, int aKeyCode, @JdkConstants.InputEventMask int aModifier, Action aAction) { - myInputMap.put(KeyStroke.getKeyStroke(aKeyCode, aModifier), aActionName); - myActionMap.put(aActionName, aAction); - } - - protected String getActionForKeyStroke(final KeyStroke keyStroke) { - return (String) myInputMap.get(keyStroke); - } - - public final void registerAction(@NonNls String aActionName, KeyStroke keyStroke, Action aAction) { - myInputMap.put(keyStroke, aActionName); - myActionMap.put(aActionName, aAction); - } - - protected abstract InputMap getInputMap(); - - protected abstract ActionMap getActionMap(); - - protected final void setParentValue(Object parentValue) { - myParentValue = parentValue; - } - - @NotNull - protected MyContentPanel createContentPanel(final boolean resizable, final PopupBorder border, final boolean isToDrawMacCorner) { - return new MyContainer(resizable, border, isToDrawMacCorner); - } - - private static class MyContainer extends MyContentPanel { - - private MyContainer(final boolean resizable, final PopupBorder border, final boolean drawMacCorner) { - super(resizable, border, drawMacCorner); - setOpaque(true); - setFocusCycleRoot(true); - } - - public Dimension getPreferredSize() { - final Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - Point p = null; - if (focusOwner != null && focusOwner.isShowing()) { - p = focusOwner.getLocationOnScreen(); - } - - return computeNotBiggerDimension(super.getPreferredSize().getSize(), p); - } - - private static Dimension computeNotBiggerDimension(Dimension ofContent, final Point locationOnScreen) { - int resultHeight = ofContent.height > MAX_SIZE.height + 50 ? MAX_SIZE.height : ofContent.height; - if (locationOnScreen != null) { - final Rectangle r = ScreenUtil.getScreenRectangle(locationOnScreen); - resultHeight = ofContent.height > r.height - (r.height / 4) ? r.height - (r.height / 4) : ofContent.height; - } - - int resultWidth = ofContent.width > MAX_SIZE.width ? MAX_SIZE.width : ofContent.width; - - if (ofContent.height > MAX_SIZE.height) { - resultWidth += ScrollPaneFactory.createScrollPane().getVerticalScrollBar().getPreferredSize().getWidth(); - } - - return new Dimension(resultWidth, resultHeight); - } - } - - public WizardPopup getParent() { - return myParent; - } - - public PopupStep getStep() { - return myStep; - } - - public final void dispatch(KeyEvent event) { - if (event.getID() != KeyEvent.KEY_PRESSED && event.getID() != KeyEvent.KEY_RELEASED) { - return; - } - - if (event.getID() == KeyEvent.KEY_PRESSED) { - final KeyStroke stroke = KeyStroke.getKeyStroke(event.getKeyCode(), event.getModifiers(), false); - if (proceedKeyEvent(event, stroke)) return; - } - - if (event.getID() == KeyEvent.KEY_RELEASED) { - final KeyStroke stroke = KeyStroke.getKeyStroke(event.getKeyCode(), event.getModifiers(), true); - proceedKeyEvent(event, stroke); - return; - } - - myMnemonicsSearch.process(event); - mySpeedSearch.process(event); - - if (event.isConsumed()) return; - process(event); - } - - private boolean proceedKeyEvent(KeyEvent event, KeyStroke stroke) { - if (myInputMap.get(stroke) != null) { - final Action action = myActionMap.get(myInputMap.get(stroke)); - if (action != null && action.isEnabled()) { - action.actionPerformed(new ActionEvent(getContent(), event.getID(), "", event.getWhen(), event.getModifiers())); - return true; - } - } - return false; - } - - protected void process(KeyEvent aEvent) { - - } - - public Rectangle getBounds() { - return new Rectangle(getContent().getLocationOnScreen(), getContent().getSize()); - } - - protected WizardPopup createPopup(WizardPopup parent, PopupStep step, Object parentValue) { - if (step instanceof ListPopupStep) { - return new ListPopupImpl(parent, (ListPopupStep)step, parentValue); - } - else if (step instanceof TreePopupStep) { - return new TreePopupImpl(parent, (TreePopupStep)step, parentValue); - } - else { - throw new IllegalArgumentException(step.getClass().toString()); - } - } - - public final void actionPerformed(ActionEvent e) { - myAutoSelectionTimer.stop(); - if (getStep().isAutoSelectionEnabled()) { - onAutoSelectionTimer(); - } - } - - protected final void restartTimer() { - if (!myAutoSelectionTimer.isRunning()) { - myAutoSelectionTimer.start(); - } - else { - myAutoSelectionTimer.restart(); - } - } - - protected final void stopTimer() { - myAutoSelectionTimer.stop(); - } - - protected void onAutoSelectionTimer() { - - } - - public boolean shouldBeShowing(Object value) { - if (!myStep.isSpeedSearchEnabled()) return true; - SpeedSearchFilter filter = myStep.getSpeedSearchFilter(); - if (!filter.canBeHidden(value)) return true; - String text = filter.getIndexedString(value); - return mySpeedSearch.shouldBeShowing(text); - } - - public SpeedSearch getSpeedSearch() { - return mySpeedSearch; - } - - - protected void onSelectByMnemonic(Object value) { - - } - - protected abstract void onChildSelectedFor(Object value); - - protected final void notifyParentOnChildSelection() { - if (myParent == null || myParentValue == null) return; - myParent.onChildSelectedFor(myParentValue); - } - - - private class MyComponentAdapter extends ComponentAdapter { - public void componentMoved(final ComponentEvent e) { - processParentWindowMoved(); - } - } - - public final void setFinalRunnable(Runnable runnable) { - if (getParent() == null) { - super.setFinalRunnable(runnable); - } else { - getParent().setFinalRunnable(runnable); - } - } - - @Override - public void setOk(boolean ok) { - if (getParent() == null) { - super.setOk(ok); - } else { - getParent().setOk(ok); - } - } -} +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.ui.popup; + +import com.intellij.ide.DataManager; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.*; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; +import com.intellij.ui.PopupBorder; +import com.intellij.ui.ScreenUtil; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.popup.list.ListPopupImpl; +import com.intellij.ui.popup.tree.TreePopupImpl; +import com.intellij.ui.popup.util.MnemonicsSearch; +import com.intellij.ui.speedSearch.ElementFilter; +import com.intellij.ui.speedSearch.SpeedSearch; +import com.intellij.util.ui.UIUtil; +import org.intellij.lang.annotations.JdkConstants; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.Collections; + +public abstract class WizardPopup extends AbstractPopup implements ActionListener, ElementFilter { + private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.WizardPopup"); + + private static final int AUTO_POPUP_DELAY = 750; + private static final Dimension MAX_SIZE = new Dimension(Integer.MAX_VALUE, 600); + + protected static final int STEP_X_PADDING = 2; + + private final WizardPopup myParent; + + protected final PopupStep myStep; + protected WizardPopup myChild; + + private final Timer myAutoSelectionTimer = UIUtil.createNamedTimer("Wizard autoselection",AUTO_POPUP_DELAY, this); + + private MnemonicsSearch myMnemonicsSearch; + private Object myParentValue; + + private Point myLastOwnerPoint; + private Window myOwnerWindow; + private MyComponentAdapter myOwnerListener; + + private final ActionMap myActionMap = new ActionMap(); + private final InputMap myInputMap = new InputMap(); + + public WizardPopup(PopupStep aStep) { + this(null, aStep); + } + + public WizardPopup(JBPopup aParent, PopupStep aStep) { + myParent = (WizardPopup) aParent; + myStep = aStep; + + mySpeedSearch.setEnabled(myStep.isSpeedSearchEnabled()); + + final JComponent content = createContent(); + + JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(content); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.getHorizontalScrollBar().setBorder(null); + + scrollPane.getActionMap().get("unitScrollLeft").setEnabled(false); + scrollPane.getActionMap().get("unitScrollRight").setEnabled(false); + + scrollPane.setBorder(null); + + final Project project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext()); + init(project, scrollPane, getPreferredFocusableComponent(), true, true, true, true, null, + false, aStep.getTitle(), null, true, null, false, null, null, null, false, null, true, false, true, null, 0f, + null, true, false, new Component[0], null, SwingUtilities.LEFT, true, Collections.>emptyList(), null, null, false, true, + true, null); + + registerAction("disposeAll", KeyEvent.VK_ESCAPE, InputEvent.SHIFT_MASK, new AbstractAction() { + public void actionPerformed(ActionEvent e) { + if (mySpeedSearch.isHoldingFilter()) { + mySpeedSearch.reset(); + } + else { + disposeAll(); + } + } + }); + + AbstractAction goBackAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + goBack(); + } + }; + + registerAction("goBack3", KeyEvent.VK_ESCAPE, 0, goBackAction); + + myMnemonicsSearch = new MnemonicsSearch(this) { + protected void select(Object value) { + onSelectByMnemonic(value); + } + }; + + + + } + + private void disposeAll() { + WizardPopup root = PopupDispatcher.getActiveRoot(); + disposeAllParents(null); + root.getStep().canceled(); + } + + public void goBack() { + if (mySpeedSearch.isHoldingFilter()) { + mySpeedSearch.reset(); + return; + } + + if (myParent != null) { + myParent.disposeChildren(); + } + else { + disposeAll(); + } + } + + protected abstract JComponent createContent(); + + public void dispose() { + super.dispose(); + + myAutoSelectionTimer.stop(); + + PopupDispatcher.unsetShowing(this); + PopupDispatcher.clearRootIfNeeded(this); + + + if (myOwnerWindow != null && myOwnerListener != null) { + myOwnerWindow.removeComponentListener(myOwnerListener); + } + } + + + public void disposeChildren() { + if (myChild != null) { + myChild.disposeChildren(); + Disposer.dispose(myChild); + myChild = null; + } + } + + public void show(final Component owner, final int aScreenX, final int aScreenY, final boolean considerForcedXY) { + LOG.assertTrue (!isDisposed()); + + Rectangle targetBounds = new Rectangle(new Point(aScreenX, aScreenY), getContent().getPreferredSize()); + ScreenUtil.moveRectangleToFitTheScreen(targetBounds); + + if (getParent() != null) { + final Rectangle parentBounds = getParent().getBounds(); + parentBounds.x += STEP_X_PADDING; + parentBounds.width -= STEP_X_PADDING * 2; + if (parentBounds.intersects(targetBounds)) { + targetBounds.x = getParent().getBounds().x - targetBounds.width - STEP_X_PADDING; + } + } + + if (getParent() == null) { + PopupDispatcher.setActiveRoot(this); + } + else { + PopupDispatcher.setShowing(this); + } + + LOG.assertTrue (!isDisposed(), "Disposed popup, parent="+getParent()); + super.show(owner, targetBounds.x, targetBounds.y, true); + } + + protected void afterShow() { + super.afterShow(); + registerAutoMove(); + + if (!myFocusTrackback.isMustBeShown()) { + cancel(); + } + } + + private void registerAutoMove() { + if (myOwner != null) { + myOwnerWindow = SwingUtilities.getWindowAncestor(myOwner); + if (myOwnerWindow != null) { + myLastOwnerPoint = myOwnerWindow.getLocationOnScreen(); + myOwnerListener = new MyComponentAdapter(); + myOwnerWindow.addComponentListener(myOwnerListener); + } + } + } + + private void processParentWindowMoved() { + if (isDisposed()) return; + + final Point newOwnerPoint = myOwnerWindow.getLocationOnScreen(); + + int deltaX = myLastOwnerPoint.x - newOwnerPoint.x; + int deltaY = myLastOwnerPoint.y - newOwnerPoint.y; + + myLastOwnerPoint = newOwnerPoint; + + final Window wnd = SwingUtilities.getWindowAncestor(getContent()); + final Point current = wnd.getLocationOnScreen(); + + setLocation(new Point(current.x - deltaX, current.y - deltaY)); + } + + protected abstract JComponent getPreferredFocusableComponent(); + + public void cancel(InputEvent e) { + super.cancel(e); + disposeChildren(); + Disposer.dispose(this); + getStep().canceled(); + } + + @Override + public boolean isCancelKeyEnabled() { + return super.isCancelKeyEnabled() && !mySpeedSearch.isHoldingFilter(); + } + + protected void disposeAllParents(InputEvent e) { + myDisposeEvent = e; + dispose(); + if (myParent != null) { + myParent.disposeAllParents(null); + } + } + + public final void registerAction(@NonNls String aActionName, int aKeyCode, @JdkConstants.InputEventMask int aModifier, Action aAction) { + myInputMap.put(KeyStroke.getKeyStroke(aKeyCode, aModifier), aActionName); + myActionMap.put(aActionName, aAction); + } + + protected String getActionForKeyStroke(final KeyStroke keyStroke) { + return (String) myInputMap.get(keyStroke); + } + + public final void registerAction(@NonNls String aActionName, KeyStroke keyStroke, Action aAction) { + myInputMap.put(keyStroke, aActionName); + myActionMap.put(aActionName, aAction); + } + + protected abstract InputMap getInputMap(); + + protected abstract ActionMap getActionMap(); + + protected final void setParentValue(Object parentValue) { + myParentValue = parentValue; + } + + @NotNull + protected MyContentPanel createContentPanel(final boolean resizable, final PopupBorder border, final boolean isToDrawMacCorner) { + return new MyContainer(resizable, border, isToDrawMacCorner); + } + + private static class MyContainer extends MyContentPanel { + + private MyContainer(final boolean resizable, final PopupBorder border, final boolean drawMacCorner) { + super(resizable, border, drawMacCorner); + setOpaque(true); + setFocusCycleRoot(true); + } + + public Dimension getPreferredSize() { + final Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + Point p = null; + if (focusOwner != null && focusOwner.isShowing()) { + p = focusOwner.getLocationOnScreen(); + } + + return computeNotBiggerDimension(super.getPreferredSize().getSize(), p); + } + + private static Dimension computeNotBiggerDimension(Dimension ofContent, final Point locationOnScreen) { + int resultHeight = ofContent.height > MAX_SIZE.height + 50 ? MAX_SIZE.height : ofContent.height; + if (locationOnScreen != null) { + final Rectangle r = ScreenUtil.getScreenRectangle(locationOnScreen); + resultHeight = ofContent.height > r.height - (r.height / 4) ? r.height - (r.height / 4) : ofContent.height; + } + + int resultWidth = ofContent.width > MAX_SIZE.width ? MAX_SIZE.width : ofContent.width; + + if (ofContent.height > MAX_SIZE.height) { + resultWidth += ScrollPaneFactory.createScrollPane().getVerticalScrollBar().getPreferredSize().getWidth(); + } + + return new Dimension(resultWidth, resultHeight); + } + } + + public WizardPopup getParent() { + return myParent; + } + + public PopupStep getStep() { + return myStep; + } + + public final void dispatch(KeyEvent event) { + if (event.getID() != KeyEvent.KEY_PRESSED && event.getID() != KeyEvent.KEY_RELEASED) { + return; + } + + if (event.getID() == KeyEvent.KEY_PRESSED) { + final KeyStroke stroke = KeyStroke.getKeyStroke(event.getKeyCode(), event.getModifiers(), false); + if (proceedKeyEvent(event, stroke)) return; + } + + if (event.getID() == KeyEvent.KEY_RELEASED) { + final KeyStroke stroke = KeyStroke.getKeyStroke(event.getKeyCode(), event.getModifiers(), true); + proceedKeyEvent(event, stroke); + return; + } + + myMnemonicsSearch.process(event); + mySpeedSearch.process(event); + + if (event.isConsumed()) return; + process(event); + } + + private boolean proceedKeyEvent(KeyEvent event, KeyStroke stroke) { + if (myInputMap.get(stroke) != null) { + final Action action = myActionMap.get(myInputMap.get(stroke)); + if (action != null && action.isEnabled()) { + action.actionPerformed(new ActionEvent(getContent(), event.getID(), "", event.getWhen(), event.getModifiers())); + return true; + } + } + return false; + } + + protected void process(KeyEvent aEvent) { + + } + + public Rectangle getBounds() { + return new Rectangle(getContent().getLocationOnScreen(), getContent().getSize()); + } + + protected WizardPopup createPopup(WizardPopup parent, PopupStep step, Object parentValue) { + if (step instanceof ListPopupStep) { + return new ListPopupImpl(parent, (ListPopupStep)step, parentValue); + } + else if (step instanceof TreePopupStep) { + return new TreePopupImpl(parent, (TreePopupStep)step, parentValue); + } + else { + throw new IllegalArgumentException(step.getClass().toString()); + } + } + + public final void actionPerformed(ActionEvent e) { + myAutoSelectionTimer.stop(); + if (getStep().isAutoSelectionEnabled()) { + onAutoSelectionTimer(); + } + } + + protected final void restartTimer() { + if (!myAutoSelectionTimer.isRunning()) { + myAutoSelectionTimer.start(); + } + else { + myAutoSelectionTimer.restart(); + } + } + + protected final void stopTimer() { + myAutoSelectionTimer.stop(); + } + + protected void onAutoSelectionTimer() { + + } + + public boolean shouldBeShowing(Object value) { + if (!myStep.isSpeedSearchEnabled()) return true; + SpeedSearchFilter filter = myStep.getSpeedSearchFilter(); + if (!filter.canBeHidden(value)) return true; + String text = filter.getIndexedString(value); + return mySpeedSearch.shouldBeShowing(text); + } + + public SpeedSearch getSpeedSearch() { + return mySpeedSearch; + } + + + protected void onSelectByMnemonic(Object value) { + + } + + protected abstract void onChildSelectedFor(Object value); + + protected final void notifyParentOnChildSelection() { + if (myParent == null || myParentValue == null) return; + myParent.onChildSelectedFor(myParentValue); + } + + + private class MyComponentAdapter extends ComponentAdapter { + public void componentMoved(final ComponentEvent e) { + processParentWindowMoved(); + } + } + + public final void setFinalRunnable(Runnable runnable) { + if (getParent() == null) { + super.setFinalRunnable(runnable); + } else { + getParent().setFinalRunnable(runnable); + } + } + + @Override + public void setOk(boolean ok) { + if (getParent() == null) { + super.setOk(ok); + } else { + getParent().setOk(ok); + } + } +}