PY-11855 Run manage.py task improvements
[idea/community.git] / python / src / com / jetbrains / python / suggestionList / SuggestionList.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.jetbrains.python.suggestionList;
17
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;
27
28 import javax.swing.*;
29 import java.awt.*;
30 import java.util.List;
31
32 /**
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}
36  *
37  * @author Ilya.Kazakevich
38  */
39 public class SuggestionList {
40   /**
41    * Listens popup close event
42    */
43   @Nullable
44   private final JBPopupListener myListener;
45   /**
46    * Popup itself
47    */
48   @Nullable
49   private JBPopup myListPopUp;
50   /**
51    * Model of suggestion list
52    */
53   private final DefaultListModel myListModel = new DefaultListModel();
54   /**
55    * Suggestion list
56    */
57   private final JList myList = new JList(myListModel);
58
59   public SuggestionList() {
60     myListener = null;
61   }
62
63   /**
64    * @param listener popup listener (will be called back when popup closed)
65    */
66   public SuggestionList(@SuppressWarnings("NullableProblems") @NotNull final JBPopupListener listener) {
67     myListener = listener;
68   }
69
70   /**
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
76    */
77   public void showSuggestions(@NotNull final SuggestionsBuilder values,
78                               @NotNull final RelativePoint displayPoint,
79                               @Nullable final String elementToSelect) {
80     showSuggestions(values.getList(), displayPoint, elementToSelect);
81   }
82
83   /**
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)
90    */
91   public void showSuggestions(@NotNull final List<List<Suggestion>> values,
92                               @NotNull final RelativePoint displayPoint,
93                               @Nullable final String elementToSelect) {
94     close();
95     myList.setCellRenderer(new MyCellRenderer());
96     myListModel.clear();
97     if (values.isEmpty()) {
98       return;
99     }
100     // Fill and select
101
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);
110         }
111       }
112     }
113     if ((elementToSelect == null) && (!myListModel.isEmpty())) {
114       myList.setSelectedIndex(0); // Select first element
115     }
116
117     // Use scroll bars
118     final JScrollPane content = new JScrollPane(myList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
119                                                 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
120     myListPopUp =
121       JBPopupFactory.getInstance().createComponentPopupBuilder(content, null)
122         .createPopup();
123     if (myListener != null) {
124       myListPopUp.addListener(myListener);
125     }
126     myListPopUp.show(displayPoint);
127   }
128
129
130   /**
131    * Close suggestion list
132    */
133   public void close() {
134     if (myListPopUp != null) {
135       myListPopUp.cancel();
136     }
137   }
138
139   /**
140    * Move selection
141    *
142    * @param directionUp up if true. Down otherwise
143    */
144   public void moveSelection(final boolean directionUp) {
145     if (myListModel.isEmpty()) {
146       return;
147     }
148     // Handle separation
149     int newIndex = myList.getSelectedIndex() + (directionUp ? -1 : 1);
150     if (newIndex < 0) {
151       newIndex = 0;
152     }
153     if (newIndex >= myListModel.size()) {
154       newIndex = myListModel.size() - 1;
155     }
156     myList.setSelectedIndex(newIndex);
157     myList.scrollRectToVisible(myList.getCellBounds(newIndex, newIndex));
158   }
159
160   /**
161    * @return currently selected value (null if nothing is selected)
162    */
163   @Nullable
164   public String getValue() {
165     if (myListPopUp == null || !myListPopUp.isVisible()) {
166       return null; // Nothing is selected if list is invisible
167     }
168     final Object value = myList.getSelectedValue();
169     return ((value == null) ? "" : getElement(value).mySuggestion.getText());
170   }
171
172   /**
173    * Element that represents suggestion
174    */
175   private static final class SuggestionListElement {
176     /**
177      * is part of odd group
178      */
179     private final boolean myOddGroup;
180     /**
181      * suggestion itself
182      */
183     @NotNull
184     private final Suggestion mySuggestion;
185
186     private SuggestionListElement(final boolean oddGroup,
187                                   @NotNull final Suggestion suggestion) {
188       myOddGroup = oddGroup;
189       mySuggestion = suggestion;
190     }
191   }
192
193   /**
194    * Cell renderer for suggestion
195    */
196   private static class MyCellRenderer extends DefaultListCellRenderer {
197     @NotNull
198     private static final Color ODD_GROUP_SELECTED_BACKGROUND_COLOR =
199       EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.SELECTED_TEARLINE_COLOR);
200     @NotNull
201     private static final Color ODD_GROUP_BACKGROUND_COLOR =
202       EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.TEARLINE_COLOR);
203
204     @Override
205     public Component getListCellRendererComponent(final JList list,
206                                                   final Object value,
207                                                   final int index,
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,
213                                                                      cellHasFocus);
214
215
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()));
220       }
221
222       if (!(component instanceof JLabel)) {
223         return component; // We can't change any property here
224       }
225
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);
230       }
231
232
233       return component;
234     }
235   }
236
237   /**
238    * Converts argument to {@link SuggestionListElement} which always should be.
239    *
240    * @param value argument to convert
241    * @return converted argument
242    */
243   @NotNull
244   private static SuggestionListElement getElement(final Object value) {
245     assert value instanceof SuggestionListElement : "Value is not valid element: " + value;
246     return (SuggestionListElement)value;
247   }
248 }