2 * Copyright 2000-2014 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.
16 package com.jetbrains.python.suggestionList;
18 import com.intellij.openapi.editor.colors.EditorColors;
19 import com.intellij.openapi.editor.colors.EditorColorsManager;
20 import com.intellij.openapi.ui.popup.JBPopup;
21 import com.intellij.openapi.ui.popup.JBPopupFactory;
22 import com.intellij.openapi.ui.popup.JBPopupListener;
23 import com.intellij.ui.awt.RelativePoint;
24 import com.intellij.util.PlatformIcons;
25 import org.jetbrains.annotations.NotNull;
26 import org.jetbrains.annotations.Nullable;
30 import java.util.List;
33 * List of suggestions to be displayed.
34 * Suggestions may be separated into groups: Group1[suggestion1, suggestion2, suggestion3].
35 * Each suggestion may also be marked as strong (see {@link com.jetbrains.python.suggestionList.Suggestion}
37 * @author Ilya.Kazakevich
39 public class SuggestionList {
41 * Listens popup close event
44 private final JBPopupListener myListener;
49 private JBPopup myListPopUp;
51 * Model of suggestion list
53 private final DefaultListModel myListModel = new DefaultListModel();
57 private final JList myList = new JList(myListModel);
59 public SuggestionList() {
64 * @param listener popup listener (will be called back when popup closed)
66 public SuggestionList(@SuppressWarnings("NullableProblems") @NotNull final JBPopupListener listener) {
67 myListener = listener;
71 * @param values suggestions wrapped into suggestion builder.
72 * Prefered usage pattern.
73 * @param displayPoint point on screen to display suggestions
74 * @param elementToSelect element in list to be selected (if any)
75 * @see com.jetbrains.python.suggestionList.SuggestionsBuilder
77 public void showSuggestions(@NotNull final SuggestionsBuilder values,
78 @NotNull final RelativePoint displayPoint,
79 @Nullable final String elementToSelect) {
80 showSuggestions(values.getList(), displayPoint, elementToSelect);
84 * @param values suggestions in format [group1[suggestions]]. See class doc for info about groups.
85 * We recommend you <strong>not to use</strong> this method.
86 * Use {@link #showSuggestions(SuggestionsBuilder, com.intellij.ui.awt.RelativePoint, String)} instead.
87 * {@link com.jetbrains.python.suggestionList.SuggestionsBuilder} is easier to use
88 * @param displayPoint point on screen to display suggestions
89 * @param elementToSelect element in list to be selected (if any)
91 public void showSuggestions(@NotNull final List<List<Suggestion>> values,
92 @NotNull final RelativePoint displayPoint,
93 @Nullable final String elementToSelect) {
95 myList.setCellRenderer(new MyCellRenderer());
97 if (values.isEmpty()) {
102 // Iterate through groups adding suggestions. Odd groups should be marked differently.
103 for (int groupId = 0; groupId < values.size(); groupId++) {
104 final List<Suggestion> suggestions = values.get(groupId);
105 for (int suggestionId = 0; suggestionId < suggestions.size(); suggestionId++) {
106 final Suggestion suggestion = suggestions.get(suggestionId);
107 myListModel.addElement(new SuggestionListElement((groupId % 2) == 0, suggestion));
108 if (suggestion.getText().equals(elementToSelect)) {
109 myList.setSelectedIndex(suggestionId + groupId);
113 if ((elementToSelect == null) && (!myListModel.isEmpty())) {
114 myList.setSelectedIndex(0); // Select first element
118 final JScrollPane content = new JScrollPane(myList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
119 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
121 JBPopupFactory.getInstance().createComponentPopupBuilder(content, null)
123 if (myListener != null) {
124 myListPopUp.addListener(myListener);
126 myListPopUp.show(displayPoint);
131 * Close suggestion list
133 public void close() {
134 if (myListPopUp != null) {
135 myListPopUp.cancel();
142 * @param directionUp up if true. Down otherwise
144 public void moveSelection(final boolean directionUp) {
145 if (myListModel.isEmpty()) {
149 int newIndex = myList.getSelectedIndex() + (directionUp ? -1 : 1);
153 if (newIndex >= myListModel.size()) {
154 newIndex = myListModel.size() - 1;
156 myList.setSelectedIndex(newIndex);
157 myList.scrollRectToVisible(myList.getCellBounds(newIndex, newIndex));
161 * @return currently selected value (null if nothing is selected)
164 public String getValue() {
165 if (myListPopUp == null || !myListPopUp.isVisible()) {
166 return null; // Nothing is selected if list is invisible
168 final Object value = myList.getSelectedValue();
169 return ((value == null) ? "" : getElement(value).mySuggestion.getText());
173 * Element that represents suggestion
175 private static final class SuggestionListElement {
177 * is part of odd group
179 private final boolean myOddGroup;
184 private final Suggestion mySuggestion;
186 private SuggestionListElement(final boolean oddGroup,
187 @NotNull final Suggestion suggestion) {
188 myOddGroup = oddGroup;
189 mySuggestion = suggestion;
194 * Cell renderer for suggestion
196 private static class MyCellRenderer extends DefaultListCellRenderer {
198 private static final Color ODD_GROUP_SELECTED_BACKGROUND_COLOR =
199 EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.SELECTED_TEARLINE_COLOR);
201 private static final Color ODD_GROUP_BACKGROUND_COLOR =
202 EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.TEARLINE_COLOR);
205 public Component getListCellRendererComponent(final JList list,
208 final boolean isSelected,
209 final boolean cellHasFocus) {
210 final SuggestionListElement element = getElement(value);
211 // Out parent always uses label
212 final Component component = super.getListCellRendererComponent(list, element.mySuggestion.getText(), index, isSelected,
216 if (element.myOddGroup) {
217 component.setBackground(isSelected ? ODD_GROUP_SELECTED_BACKGROUND_COLOR : ODD_GROUP_BACKGROUND_COLOR);
218 final Font oldFont = component.getFont();
219 component.setFont(new Font(oldFont.getName(), Font.ITALIC, oldFont.getSize()));
222 if (!(component instanceof JLabel)) {
223 return component; // We can't change any property here
226 final JLabel label = (JLabel)component;
227 if (element.mySuggestion.isStrong()) {
228 // We use icons to display "strong" element
229 label.setIcon(PlatformIcons.FOLDER_ICON);
238 * Converts argument to {@link SuggestionListElement} which always should be.
240 * @param value argument to convert
241 * @return converted argument
244 private static SuggestionListElement getElement(final Object value) {
245 assert value instanceof SuggestionListElement : "Value is not valid element: " + value;
246 return (SuggestionListElement)value;