2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.ide.util.gotoByName;
19 import com.intellij.featureStatistics.FeatureUsageTracker;
20 import com.intellij.ide.ui.UISettings;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.ModalityState;
23 import com.intellij.openapi.keymap.KeymapUtil;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.ui.popup.ComponentPopupBuilder;
26 import com.intellij.openapi.ui.popup.JBPopupFactory;
27 import com.intellij.openapi.util.Computable;
28 import com.intellij.openapi.util.Key;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.psi.PsiElement;
31 import com.intellij.psi.statistics.StatisticsInfo;
32 import com.intellij.psi.statistics.StatisticsManager;
33 import com.intellij.ui.ScreenUtil;
34 import org.jetbrains.annotations.NonNls;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
40 import java.awt.event.InputEvent;
41 import java.awt.event.KeyEvent;
42 import java.awt.event.MouseListener;
43 import java.util.List;
45 import java.util.regex.Matcher;
46 import java.util.regex.Pattern;
48 public class ChooseByNamePopup extends ChooseByNameBase implements ChooseByNamePopupComponent {
49 public static final Key<ChooseByNamePopup> CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY = new Key<ChooseByNamePopup>("ChooseByNamePopup");
50 private Component myOldFocusOwner = null;
51 private boolean myShowListForEmptyPattern = false;
52 private final boolean myMayRequestCurrentWindow;
53 private final ChooseByNamePopup myOldPopup;
54 private ActionMap myActionMap;
55 private InputMap myInputMap;
56 private String myAdText;
58 protected ChooseByNamePopup(@Nullable final Project project,
59 @NotNull ChooseByNameModel model,
60 @NotNull ChooseByNameItemProvider provider,
61 @Nullable ChooseByNamePopup oldPopup,
62 @Nullable final String predefinedText,
63 boolean mayRequestOpenInCurrentWindow,
65 super(project, model, provider, oldPopup != null ? oldPopup.getEnteredText() : predefinedText, initialIndex);
66 myOldPopup = oldPopup;
67 if (oldPopup != null) { //inherit old focus owner
68 myOldFocusOwner = oldPopup.myPreviouslyFocusedComponent;
70 myMayRequestCurrentWindow = mayRequestOpenInCurrentWindow;
71 myAdText = myMayRequestCurrentWindow ? "Press " +
72 KeymapUtil.getKeystrokeText(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_MASK)) +
73 " to open in current window" : null;
76 public String getEnteredText() {
77 return myTextField.getText();
80 public int getSelectedIndex() {
81 return myList.getSelectedIndex();
85 protected void initUI(final Callback callback, final ModalityState modalityState, boolean allowMultipleSelection) {
86 super.initUI(callback, modalityState, allowMultipleSelection);
87 if (myOldPopup != null) {
88 myTextField.setCaretPosition(myOldPopup.myTextField.getCaretPosition());
90 if (myInitialText != null) {
91 int selStart = myOldPopup == null ? 0 : myOldPopup.myTextField.getSelectionStart();
92 int selEnd = myOldPopup == null ? myInitialText.length() : myOldPopup.myTextField.getSelectionEnd();
93 if (selEnd > selStart) {
94 myTextField.select(selStart, selEnd);
96 rebuildList(myInitialIndex, 0, ModalityState.current(), null);
98 if (myOldFocusOwner != null) {
99 myPreviouslyFocusedComponent = myOldFocusOwner;
100 myOldFocusOwner = null;
103 if (myInputMap != null && myActionMap != null) {
104 for (KeyStroke keyStroke : myInputMap.keys()) {
105 Object key = myInputMap.get(keyStroke);
106 myTextField.getInputMap().put(keyStroke, key);
107 myTextField.getActionMap().put(key, myActionMap.get(key));
113 public boolean isOpenInCurrentWindowRequested() {
114 return super.isOpenInCurrentWindowRequested() && myMayRequestCurrentWindow;
118 protected boolean isCheckboxVisible() {
123 protected boolean isShowListForEmptyPattern(){
124 return myShowListForEmptyPattern;
127 public void setShowListForEmptyPattern(boolean showListForEmptyPattern) {
128 myShowListForEmptyPattern = showListForEmptyPattern;
132 protected boolean isCloseByFocusLost() {
133 return UISettings.getInstance().HIDE_NAVIGATION_ON_FOCUS_LOSS;
137 protected void showList() {
138 final JLayeredPane layeredPane = myTextField.getRootPane().getLayeredPane();
140 Rectangle bounds = new Rectangle(layeredPane.getLocationOnScreen(), myTextField.getSize());
141 bounds.y += layeredPane.getHeight();
143 final Dimension preferredScrollPaneSize = myListScrollPane.getPreferredSize();
144 if (myList.getModel().getSize() == 0) {
145 preferredScrollPaneSize.height = UIManager.getFont("Label.font").getSize();
148 preferredScrollPaneSize.width = Math.max(myTextFieldPanel.getWidth(), preferredScrollPaneSize.width);
150 Rectangle preferredBounds = new Rectangle(bounds.x, bounds.y, preferredScrollPaneSize.width, preferredScrollPaneSize.height);
151 Rectangle original = new Rectangle(preferredBounds);
153 ScreenUtil.fitToScreen(preferredBounds);
154 if (original.width > preferredBounds.width) {
155 int height = myListScrollPane.getHorizontalScrollBar().getPreferredSize().height;
156 preferredBounds.height += height;
159 myListScrollPane.setVisible(true);
160 myListScrollPane.setBorder(null);
161 String adText = getAdText();
162 if (myDropdownPopup == null) {
163 ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(myListScrollPane, myListScrollPane);
164 builder.setFocusable(false)
165 .setLocateWithinScreenBounds(false)
166 .setRequestFocus(false)
167 .setCancelKeyEnabled(false)
168 .setFocusOwners(new JComponent[]{myTextField})
169 .setBelongsToGlobalPopupStack(false)
170 .setModalContext(false)
172 .setMayBeParent(true);
173 builder.setCancelCallback(new Computable<Boolean>() {
175 public Boolean compute() {
179 myDropdownPopup = builder.createPopup();
180 myDropdownPopup.setLocation(preferredBounds.getLocation());
181 myDropdownPopup.setSize(preferredBounds.getSize());
182 myDropdownPopup.show(layeredPane);
185 myDropdownPopup.setLocation(preferredBounds.getLocation());
187 // in 'focus follows mouse' mode, to avoid focus escaping to editor, don't reduce popup size when list size is reduced
188 final Dimension currentSize = myDropdownPopup.getSize();
189 if (UISettings.getInstance().HIDE_NAVIGATION_ON_FOCUS_LOSS ||
190 preferredBounds.width > currentSize.width || preferredBounds.height > currentSize.height) {
191 myDropdownPopup.setSize(preferredBounds.getSize());
197 protected void hideList() {
198 if (myDropdownPopup != null) {
199 myDropdownPopup.cancel();
200 myDropdownPopup = null;
205 public void close(final boolean isOk) {
206 if (checkDisposed()){
211 myModel.saveInitialCheckBoxState(myCheckBox.isSelected());
213 final List<Object> chosenElements = getChosenElements();
214 if (chosenElements != null) {
215 if (myActionListener instanceof MultiElementsCallback) {
216 ((MultiElementsCallback)myActionListener).elementsChosen(chosenElements);
219 for (Object element : chosenElements) {
220 myActionListener.elementChosen(element);
221 String text = myModel.getFullName(element);
223 StatisticsManager.getInstance().incUseCount(new StatisticsInfo(statisticsContext(), text));
232 if (!chosenElements.isEmpty()) {
233 final String enteredText = getTrimmedText();
234 if (enteredText.indexOf('*') >= 0) {
235 FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.wildcards");
238 for (Object element : chosenElements) {
239 final String name = myModel.getElementName(element);
241 if (!StringUtil.startsWithIgnoreCase(name, enteredText)) {
242 FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.camelprefix");
255 myAlarm.cancelAllRequests();
256 if (myProject != null) {
257 myProject.putUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY, null);
261 if (ApplicationManager.getApplication().isUnitTestMode()) return;
262 if (myActionListener != null) {
263 myActionListener.onClose();
267 private void cleanupUI(boolean ok) {
268 if (myTextPopup != null) {
270 myTextPopup.closeOk(null);
273 myTextPopup.cancel();
278 if (myDropdownPopup != null) {
280 myDropdownPopup.closeOk(null);
283 myDropdownPopup.cancel();
285 myDropdownPopup = null;
289 public static ChooseByNamePopup createPopup(final Project project, final ChooseByNameModel model, final PsiElement context) {
290 return createPopup(project, model, new DefaultChooseByNameItemProvider(context), null);
293 public static ChooseByNamePopup createPopup(final Project project, final ChooseByNameModel model, final PsiElement context,
294 @Nullable final String predefinedText) {
295 return createPopup(project, model, new DefaultChooseByNameItemProvider(context), predefinedText, false, 0);
298 public static ChooseByNamePopup createPopup(final Project project, final ChooseByNameModel model, final PsiElement context,
299 @Nullable final String predefinedText,
300 boolean mayRequestOpenInCurrentWindow, final int initialIndex) {
301 return createPopup(project, model, new DefaultChooseByNameItemProvider(context), predefinedText, mayRequestOpenInCurrentWindow,
305 public static ChooseByNamePopup createPopup(final Project project,
306 @NotNull ChooseByNameModel model,
307 @NotNull ChooseByNameItemProvider provider) {
308 return createPopup(project, model, provider, null);
311 public static ChooseByNamePopup createPopup(final Project project,
312 @NotNull ChooseByNameModel model,
313 @NotNull ChooseByNameItemProvider provider,
314 @Nullable final String predefinedText) {
315 return createPopup(project, model, provider, predefinedText, false, 0);
318 public static ChooseByNamePopup createPopup(final Project project,
319 @NotNull final ChooseByNameModel model,
320 @NotNull ChooseByNameItemProvider provider,
321 @Nullable final String predefinedText,
322 boolean mayRequestOpenInCurrentWindow,
323 final int initialIndex) {
324 final ChooseByNamePopup oldPopup = project == null ? null : project.getUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY);
325 if (oldPopup != null) {
326 oldPopup.close(false);
328 ChooseByNamePopup newPopup = new ChooseByNamePopup(project, model, provider, oldPopup, predefinedText, mayRequestOpenInCurrentWindow, initialIndex) {
331 protected Set<Object> filter(@NotNull Set<Object> elements) {
332 return model instanceof EdtSortingModel ? super.filter(((EdtSortingModel)model).sort(elements)) : super.filter(elements);
336 if (project != null) {
337 project.putUserData(CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY, newPopup);
342 private static final Pattern patternToDetectLinesAndColumns = Pattern.compile("(" +
343 "(?:\\w:)?" + // absolute path start on Windows
345 ")" + // main part before line/column
346 "(?::|@|,|)\\[?(\\d+)?(?:(?:\\D)(\\d+)?)?\\]?");
347 public static final Pattern patternToDetectAnonymousClasses = Pattern.compile("([\\.\\w]+)((\\$[\\d]+)*(\\$)?)");
348 private static final Pattern patternToDetectMembers = Pattern.compile("(.+)(#)(.*)");
351 public String transformPattern(String pattern) {
352 final ChooseByNameModel model = getModel();
353 return getTransformedPattern(pattern, model);
356 public static String getTransformedPattern(String pattern, ChooseByNameModel model) {
357 Pattern regex = null;
358 if (pattern.indexOf(':') != -1 ||
359 pattern.indexOf(',') != -1 ||
360 pattern.indexOf(';') != -1 ||
361 //pattern.indexOf('#') != -1 ||
362 pattern.indexOf('@') != -1) { // quick test if reg exp should be used
363 regex = patternToDetectLinesAndColumns;
366 if (model instanceof GotoClassModel2) {
367 if (pattern.indexOf('#') != -1) {
368 regex = patternToDetectMembers;
371 if (pattern.indexOf('$') != -1) {
372 regex = patternToDetectAnonymousClasses;
377 final Matcher matcher = regex.matcher(pattern);
378 if (matcher.matches()) {
379 pattern = matcher.group(1);
386 public int getLinePosition() {
387 return getLineOrColumn(true);
390 private int getLineOrColumn(final boolean line) {
391 final Matcher matcher = patternToDetectLinesAndColumns.matcher(getTrimmedText());
392 if (matcher.matches()) {
393 final int groupNumber = line ? 2 : 3;
395 if (groupNumber <= matcher.groupCount()) {
396 final String group = matcher.group(groupNumber);
397 if (group != null) return Integer.parseInt(group) - 1;
399 if (!line && getLineOrColumn(true) != -1) return 0;
401 catch (NumberFormatException ignored) {
409 public String getPathToAnonymous() {
410 final Matcher matcher = patternToDetectAnonymousClasses.matcher(getTrimmedText());
411 if (matcher.matches()) {
412 String path = matcher.group(2);
415 if (path.endsWith("$") && path.length() >= 2) {
416 path = path.substring(0, path.length() - 2);
418 if (!path.isEmpty()) return path;
425 public int getColumnPosition() {
426 return getLineOrColumn(false);
430 public String getMemberPattern() {
431 final String enteredText = getTrimmedText();
432 final int index = enteredText.lastIndexOf('#');
437 String name = enteredText.substring(index + 1).trim();
438 return StringUtil.isEmpty(name) ? null : name;
441 public void registerAction(@NonNls String aActionName, KeyStroke keyStroke, Action aAction) {
442 if (myInputMap == null) myInputMap = new InputMap();
443 if (myActionMap == null) myActionMap = new ActionMap();
444 myInputMap.put(keyStroke, aActionName);
445 myActionMap.put(aActionName, aAction);
448 public String getAdText() {
452 public void setAdText(final String adText) {
456 public void addMouseClickListener(MouseListener listener) {
457 myList.addMouseListener(listener);
460 public Object getSelectionByPoint(Point point) {
461 final int index = myList.locationToIndex(point);
462 return index > -1 ? myList.getModel().getElementAt(index) : null;
465 public void repaintList() {