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);
This commit is contained in:
Denis.Zhdanov
2012-07-05 09:56:16 +04:00
parent bc81ff34db
commit 526c5ff8aa
7 changed files with 1009 additions and 927 deletions
@@ -49,9 +49,9 @@ public abstract class DockablePopupManager<T extends JComponent & Disposable> {
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;
}
@@ -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<DocumentationComp
@NonNls private static final String DOC_ELEMENT_PROTOCOL = "doc_element://";
private final ActionManagerEx myActionManagerEx;
private boolean myCloseOnSneeze;
private static final int ourFlagsForTargetElements = TargetElementUtilBase.getInstance().getAllAccepted();
@@ -135,11 +138,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
if (action == myActionManagerEx.getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_PAGE_UP)) return;
if (action == ActionManagerEx.getInstanceEx().getAction(IdeActions.ACTION_EDITOR_ESCAPE)) return;
if (ActionPlaces.JAVADOC_INPLACE_SETTINGS.equals(event.getPlace())) return;
Component toFocus = myPreviouslyFocused;
hint.cancel();
if (toFocus != null) {
IdeFocusManager.getInstance(project).requestFocus(toFocus, true);
}
closeDocHint();
}
}
@@ -158,12 +157,46 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
myUpdateDocAlarm = new Alarm(Alarm.ThreadToUse.OWN_THREAD,myProject);
}
private void closeDocHint() {
JBPopup hint = getDocInfoHint();
if (hint == null) {
return;
}
myCloseOnSneeze = false;
hint.cancel();
Component toFocus = myPreviouslyFocused;
hint.cancel();
if (toFocus != null) {
IdeFocusManager.getInstance(myProject).requestFocus(toFocus, true);
}
}
public boolean hasDockedDocWindow() {
return myToolWindow != null;
}
/**
* Asks to show quick doc for the target element.
*
* @param editor editor with an element for which quick do should be shown
* @param element target element which documentation should be shown
* @param original element that was used as a quick doc anchor. Example: consider a code like {@code Runnable task;}.
* A user wants to see javadoc for the {@code Runnable}, so, original element is a class name from the variable
* declaration but <code>'element'</code> 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<DocumentationComp
.setModalContext(false)
.setCancelCallback(new Computable<Boolean>() {
public Boolean compute() {
myCloseOnSneeze = false;
if (closeCallback != null) {
closeCallback.run();
}
@@ -374,6 +408,15 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
return Boolean.TRUE;
}
})
.setKeyEventHandler(new BooleanFunction<KeyEvent>() {
@Override
public boolean fun(KeyEvent event) {
if (myCloseOnSneeze) {
closeDocHint();
}
return false;
}
})
.createPopup();
@@ -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<DocumentationManager>(info.docManager);
}
finally {
@@ -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<Project> 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<Boolean> 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<JBPopup> callback);
@NotNull
ComponentPopupBuilder setKeyboardActions(@NotNull List<Pair<ActionListener, KeyStroke>> 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<Project> 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<Boolean> 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<JBPopup> callback);
@NotNull
ComponentPopupBuilder setKeyboardActions(@NotNull List<Pair<ActionListener, KeyStroke>> 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 <code>this</code>
*/
@NotNull
ComponentPopupBuilder setKeyEventHandler(@NotNull BooleanFunction<KeyEvent> handler);
}
@@ -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.
* <p/>
* 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 <code>true</code> if the event is completely dispatched, i.e. no further processing is necessary;
* <code>false</code> otherwise
*/
boolean dispatchKeyEvent(@NotNull KeyEvent e);
}
@@ -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<Boolean> myCallback = null;
private Project myProject;
private boolean myCancelOnClickOutside = true;
private boolean myCancelOnWindowDeactivation = true;
private final Set<JBPopupListener> myListeners = new LinkedHashSet<JBPopupListener>();
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<JBPopup> myPinCallback = null;
private Dimension myMinSize;
private MaskProvider myMaskProvider;
private float myAlpha;
private ArrayList<Object> 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<Pair<ActionListener, KeyStroke>> 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<Boolean> 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<JBPopup> callback) {
myPinCallback = callback;
return this;
}
@NotNull
public ComponentPopupBuilder setKeyboardActions(@NotNull List<Pair<ActionListener, KeyStroke>> 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<Project> 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<Object>();
}
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<Boolean> myCallback = null;
private Project myProject;
private boolean myCancelOnClickOutside = true;
private boolean myCancelOnWindowDeactivation = true;
private final Set<JBPopupListener> myListeners = new LinkedHashSet<JBPopupListener>();
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<JBPopup> myPinCallback = null;
private Dimension myMinSize;
private MaskProvider myMaskProvider;
private float myAlpha;
private ArrayList<Object> 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<Pair<ActionListener, KeyStroke>> myKeyboardActions = Collections.emptyList();
private Component mySettingsButtons;
private boolean myMayBeParent;
private int myAdAlignment = SwingUtilities.LEFT;
private BooleanFunction<KeyEvent> 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<Boolean> 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<JBPopup> callback) {
myPinCallback = callback;
return this;
}
@NotNull
public ComponentPopupBuilder setKeyboardActions(@NotNull List<Pair<ActionListener, KeyStroke>> 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<KeyEvent> 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<Project> 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<Object>();
}
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;
}
}
@@ -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<Object> 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<Object> aStep) {
this(null, aStep);
}
public WizardPopup(JBPopup aParent, PopupStep<Object> 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.<Pair<ActionListener, KeyStroke>>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<Object> 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<Object> 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<Object> aStep) {
this(null, aStep);
}
public WizardPopup(JBPopup aParent, PopupStep<Object> 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.<Pair<ActionListener, KeyStroke>>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<Object> 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);
}
}
}