blob: 86d9c1782b2ae7837e65c0b9f4ebff62a8e4d76a [file] [log] [blame]
/*
* Copyright 2000-2014 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.ide.util.gotoByName;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.ComponentPopupBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.statistics.StatisticsInfo;
import com.intellij.psi.statistics.StatisticsManager;
import com.intellij.ui.ScreenUtil;
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.InputEvent;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ChooseByNamePopup extends ChooseByNameBase implements ChooseByNamePopupComponent {
public static final Key<ChooseByNamePopup> CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY = new Key<ChooseByNamePopup>("ChooseByNamePopup");
private Component myOldFocusOwner = null;
private boolean myShowListForEmptyPattern = false;
private final boolean myMayRequestCurrentWindow;
private final ChooseByNamePopup myOldPopup;
private ActionMap myActionMap;
private InputMap myInputMap;
private String myAdText;
protected ChooseByNamePopup(@Nullable final Project project,
@NotNull ChooseByNameModel model,
@NotNull ChooseByNameItemProvider provider,
@Nullable ChooseByNamePopup oldPopup,
@Nullable final String predefinedText,
boolean mayRequestOpenInCurrentWindow,
int initialIndex) {
super(project, model, provider, oldPopup != null ? oldPopup.getEnteredText() : predefinedText, initialIndex);
myOldPopup = oldPopup;
if (oldPopup != null) { //inherit old focus owner
myOldFocusOwner = oldPopup.myPreviouslyFocusedComponent;
}
myMayRequestCurrentWindow = mayRequestOpenInCurrentWindow;
myAdText = myMayRequestCurrentWindow ? "Press " +
KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_MASK)) +
" to open in current window" : null;
}
public String getEnteredText() {
return myTextField.getText();
}
public int getSelectedIndex() {
return myList.getSelectedIndex();
}
@Override
protected void initUI(final Callback callback, final ModalityState modalityState, boolean allowMultipleSelection) {
super.initUI(callback, modalityState, allowMultipleSelection);
if (myOldPopup != null) {
myTextField.setCaretPosition(myOldPopup.myTextField.getCaretPosition());
}
if (myInitialText != null) {
int selStart = myOldPopup == null ? 0 : myOldPopup.myTextField.getSelectionStart();
int selEnd = myOldPopup == null ? myInitialText.length() : myOldPopup.myTextField.getSelectionEnd();
if (selEnd > selStart) {
myTextField.select(selStart, selEnd);
}
rebuildList(myInitialIndex, 0, ModalityState.current(), null);
}
if (myOldFocusOwner != null) {
myPreviouslyFocusedComponent = myOldFocusOwner;
myOldFocusOwner = null;
}
if (myInputMap != null && myActionMap != null) {
for (KeyStroke keyStroke : myInputMap.keys()) {
Object key = myInputMap.get(keyStroke);
myTextField.getInputMap().put(keyStroke, key);
myTextField.getActionMap().put(key, myActionMap.get(key));
}
}
}
@Override
public boolean isOpenInCurrentWindowRequested() {
return super.isOpenInCurrentWindowRequested() && myMayRequestCurrentWindow;
}
@Override
protected boolean isCheckboxVisible() {
return true;
}
@Override
protected boolean isShowListForEmptyPattern(){
return myShowListForEmptyPattern;
}
public void setShowListForEmptyPattern(boolean showListForEmptyPattern) {
myShowListForEmptyPattern = showListForEmptyPattern;
}
@Override
protected boolean isCloseByFocusLost() {
return UISettings.getInstance().HIDE_NAVIGATION_ON_FOCUS_LOSS;
}
@Override
protected void showList() {
final JLayeredPane layeredPane = myTextField.getRootPane().getLayeredPane();
Rectangle bounds = new Rectangle(myTextFieldPanel.getLocationOnScreen(), myTextField.getSize());
bounds.y += myTextFieldPanel.getHeight() + (SystemInfo.isMac ? 3 : 1);
final Dimension preferredScrollPaneSize = myListScrollPane.getPreferredSize();
if (myList.getModel().getSize() == 0) {
preferredScrollPaneSize.height = UIManager.getFont("Label.font").getSize();
}
preferredScrollPaneSize.width = Math.max(myTextFieldPanel.getWidth(), preferredScrollPaneSize.width);
Rectangle preferredBounds = new Rectangle(bounds.x, bounds.y, preferredScrollPaneSize.width, preferredScrollPaneSize.height);
Rectangle original = new Rectangle(preferredBounds);
ScreenUtil.fitToScreen(preferredBounds);
if (original.width > preferredBounds.width) {
int height = myListScrollPane.getHorizontalScrollBar().getPreferredSize().height;
preferredBounds.height += height;
}
myListScrollPane.setVisible(true);
myListScrollPane.setBorder(null);
String adText = getAdText();
if (myDropdownPopup == null) {
ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(myListScrollPane, myListScrollPane);
builder.setFocusable(false)
.setRequestFocus(false)
.setCancelKeyEnabled(false)
.setFocusOwners(new JComponent[]{myTextField})
.setBelongsToGlobalPopupStack(false)
.setModalContext(false)
.setAdText(adText)
.setMayBeParent(true);
builder.setCancelCallback(new Computable<Boolean>() {
@Override
public Boolean compute() {
return Boolean.TRUE;
}
});
myDropdownPopup = builder.createPopup();
myDropdownPopup.setLocation(preferredBounds.getLocation());
myDropdownPopup.setSize(preferredBounds.getSize());
myDropdownPopup.show(layeredPane);
}
else {
myDropdownPopup.setLocation(preferredBounds.getLocation());
// in 'focus follows mouse' mode, to avoid focus escaping to editor, don't reduce popup size when list size is reduced
final Dimension currentSize = myDropdownPopup.getSize();
if (UISettings.getInstance().HIDE_NAVIGATION_ON_FOCUS_LOSS ||
preferredBounds.width > currentSize.width || preferredBounds.height > currentSize.height) {
myDropdownPopup.setSize(preferredBounds.getSize());
}
}
}
@Override
protected void hideList() {
if (myDropdownPopup != null) {
myDropdownPopup.cancel();
myDropdownPopup = null;
}
}
@Override
public void close(final boolean isOk) {
if (checkDisposed()){
return;
}
if (isOk) {
myModel.saveInitialCheckBoxState(myCheckBox.isSelected());
final List<Object> chosenElements = getChosenElements();
if (chosenElements != null) {
if (myActionListener instanceof MultiElementsCallback) {
((MultiElementsCallback)myActionListener).elementsChosen(chosenElements);
}
else {
for (Object element : chosenElements) {
myActionListener.elementChosen(element);
String text = myModel.getFullName(element);
if (text != null) {
StatisticsManager.getInstance().incUseCount(new StatisticsInfo(statisticsContext(), text));
}
}
}
}
else {
return;
}
if (!chosenElements.isEmpty()) {
final String enteredText = getEnteredText();
if (enteredText.indexOf('*') >= 0) {
FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.wildcards");
}
else {
for (Object element : chosenElements) {
final String name = myModel.getElementName(element);
if (name != null) {
if (!StringUtil.startsWithIgnoreCase(name, enteredText)) {
FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.camelprefix");
break;
}
}
}
}
}
else {
return;
}
}
setDisposed(true);
myAlarm.cancelAllRequests();
if (myProject != null) {
myProject.putUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY, null);
}
cleanupUI(isOk);
if (ApplicationManager.getApplication().isUnitTestMode()) return;
if (myActionListener != null) {
myActionListener.onClose();
}
}
private void cleanupUI(boolean ok) {
if (myTextPopup != null) {
if (ok) {
myTextPopup.closeOk(null);
}
else {
myTextPopup.cancel();
}
myTextPopup = null;
}
if (myDropdownPopup != null) {
if (ok) {
myDropdownPopup.closeOk(null);
}
else {
myDropdownPopup.cancel();
}
myDropdownPopup = null;
}
}
public static ChooseByNamePopup createPopup(final Project project, final ChooseByNameModel model, final PsiElement context) {
return createPopup(project, model, new DefaultChooseByNameItemProvider(context), null);
}
public static ChooseByNamePopup createPopup(final Project project, final ChooseByNameModel model, final PsiElement context,
@Nullable final String predefinedText) {
return createPopup(project, model, new DefaultChooseByNameItemProvider(context), predefinedText, false, 0);
}
public static ChooseByNamePopup createPopup(final Project project, final ChooseByNameModel model, final PsiElement context,
@Nullable final String predefinedText,
boolean mayRequestOpenInCurrentWindow, final int initialIndex) {
return createPopup(project, model, new DefaultChooseByNameItemProvider(context), predefinedText, mayRequestOpenInCurrentWindow,
initialIndex);
}
public static ChooseByNamePopup createPopup(final Project project,
@NotNull ChooseByNameModel model,
@NotNull ChooseByNameItemProvider provider) {
return createPopup(project, model, provider, null);
}
public static ChooseByNamePopup createPopup(final Project project,
@NotNull ChooseByNameModel model,
@NotNull ChooseByNameItemProvider provider,
@Nullable final String predefinedText) {
return createPopup(project, model, provider, predefinedText, false, 0);
}
public static ChooseByNamePopup createPopup(final Project project,
@NotNull final ChooseByNameModel model,
@NotNull ChooseByNameItemProvider provider,
@Nullable final String predefinedText,
boolean mayRequestOpenInCurrentWindow,
final int initialIndex) {
final ChooseByNamePopup oldPopup = project == null ? null : project.getUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY);
if (oldPopup != null) {
oldPopup.close(false);
}
ChooseByNamePopup newPopup = new ChooseByNamePopup(project, model, provider, oldPopup, predefinedText, mayRequestOpenInCurrentWindow, initialIndex) {
@NotNull
@Override
protected Set<Object> filter(@NotNull Set<Object> elements) {
return model instanceof EdtSortingModel ? super.filter(((EdtSortingModel)model).sort(elements)) : super.filter(elements);
}
};
if (project != null) {
project.putUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY, newPopup);
}
return newPopup;
}
private static final Pattern patternToDetectLinesAndColumns = Pattern.compile("([^:]+)(?::|@|,|)\\[?(\\d+)?(?:(?:\\D)(\\d+)?)?\\]?");
public static final Pattern patternToDetectAnonymousClasses = Pattern.compile("([\\.\\w]+)((\\$[\\d]+)*(\\$)?)");
private static final Pattern patternToDetectMembers = Pattern.compile("(.+)(#)(.*)");
@Override
public String transformPattern(String pattern) {
final ChooseByNameModel model = getModel();
return getTransformedPattern(pattern, model);
}
public static String getTransformedPattern(String pattern, ChooseByNameModel model) {
Pattern regex = null;
if (pattern.indexOf(':') != -1 ||
pattern.indexOf(',') != -1 ||
pattern.indexOf(';') != -1 ||
//pattern.indexOf('#') != -1 ||
pattern.indexOf('@') != -1) { // quick test if reg exp should be used
regex = patternToDetectLinesAndColumns;
}
if (model instanceof GotoClassModel2) {
if (pattern.indexOf('#') != -1) {
regex = patternToDetectMembers;
}
if (pattern.indexOf('$') != -1) {
regex = patternToDetectAnonymousClasses;
}
}
if (regex != null) {
final Matcher matcher = regex.matcher(pattern);
if (matcher.matches()) {
pattern = matcher.group(1);
}
}
return pattern;
}
public int getLinePosition() {
return getLineOrColumn(true);
}
private int getLineOrColumn(final boolean line) {
final Matcher matcher = patternToDetectLinesAndColumns.matcher(getEnteredText());
if (matcher.matches()) {
final int groupNumber = line ? 2 : 3;
try {
if (groupNumber <= matcher.groupCount()) {
final String group = matcher.group(groupNumber);
if (group != null) return Integer.parseInt(group) - 1;
}
if (!line && getLineOrColumn(true) != -1) return 0;
}
catch (NumberFormatException ignored) {
}
}
return -1;
}
@Nullable
public String getPathToAnonymous() {
final Matcher matcher = patternToDetectAnonymousClasses.matcher(getEnteredText());
if (matcher.matches()) {
String path = matcher.group(2);
if (path != null) {
path = path.trim();
if (path.endsWith("$") && path.length() >= 2) {
path = path.substring(0, path.length() - 2);
}
if (!path.isEmpty()) return path;
}
}
return null;
}
public int getColumnPosition() {
return getLineOrColumn(false);
}
@Nullable
public String getMemberPattern() {
final int index = getEnteredText().lastIndexOf('#');
if (index == -1) {
return null;
}
String name = getEnteredText().substring(index + 1).trim();
return StringUtil.isEmpty(name) ? null : name;
}
public void registerAction(@NonNls String aActionName, KeyStroke keyStroke, Action aAction) {
if (myInputMap == null) myInputMap = new InputMap();
if (myActionMap == null) myActionMap = new ActionMap();
myInputMap.put(keyStroke, aActionName);
myActionMap.put(aActionName, aAction);
}
public String getAdText() {
return myAdText;
}
public void setAdText(final String adText) {
myAdText = adText;
}
}