a2eaae64f8517b9c62afb1e141afcfb384222a81
[idea/community.git] / platform / lang-impl / src / com / intellij / find / actions / ShowUsagesAction.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
17 package com.intellij.find.actions;
18
19 import com.intellij.codeInsight.hint.HintManager;
20 import com.intellij.codeInsight.hint.HintUtil;
21 import com.intellij.featureStatistics.FeatureUsageTracker;
22 import com.intellij.find.FindManager;
23 import com.intellij.find.FindSettings;
24 import com.intellij.find.UsagesPreviewPanelProvider;
25 import com.intellij.find.findUsages.*;
26 import com.intellij.find.impl.FindManagerImpl;
27 import com.intellij.icons.AllIcons;
28 import com.intellij.ide.DataManager;
29 import com.intellij.ide.util.gotoByName.ModelDiff;
30 import com.intellij.openapi.Disposable;
31 import com.intellij.openapi.actionSystem.*;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.editor.Editor;
34 import com.intellij.openapi.fileEditor.FileEditor;
35 import com.intellij.openapi.fileEditor.FileEditorLocation;
36 import com.intellij.openapi.fileEditor.TextEditor;
37 import com.intellij.openapi.keymap.KeymapUtil;
38 import com.intellij.openapi.preview.PreviewManager;
39 import com.intellij.openapi.progress.ProgressIndicator;
40 import com.intellij.openapi.project.DumbAwareAction;
41 import com.intellij.openapi.project.Project;
42 import com.intellij.openapi.ui.MessageType;
43 import com.intellij.openapi.ui.popup.JBPopup;
44 import com.intellij.openapi.ui.popup.JBPopupFactory;
45 import com.intellij.openapi.ui.popup.PopupChooserBuilder;
46 import com.intellij.openapi.util.*;
47 import com.intellij.openapi.util.text.StringUtil;
48 import com.intellij.openapi.vfs.VirtualFile;
49 import com.intellij.openapi.wm.IdeFocusManager;
50 import com.intellij.pom.Navigatable;
51 import com.intellij.psi.PsiDocumentManager;
52 import com.intellij.psi.PsiElement;
53 import com.intellij.psi.search.GlobalSearchScope;
54 import com.intellij.psi.search.PsiElementProcessor;
55 import com.intellij.psi.search.SearchScope;
56 import com.intellij.ui.*;
57 import com.intellij.ui.awt.RelativePoint;
58 import com.intellij.ui.popup.AbstractPopup;
59 import com.intellij.usageView.UsageInfo;
60 import com.intellij.usageView.UsageViewBundle;
61 import com.intellij.usageView.UsageViewUtil;
62 import com.intellij.usages.*;
63 import com.intellij.usages.impl.*;
64 import com.intellij.usages.rules.UsageFilteringRuleProvider;
65 import com.intellij.util.Alarm;
66 import com.intellij.util.PlatformIcons;
67 import com.intellij.util.Processor;
68 import com.intellij.util.messages.MessageBusConnection;
69 import com.intellij.util.ui.AsyncProcessIcon;
70 import com.intellij.util.ui.ColumnInfo;
71 import com.intellij.util.ui.ListTableModel;
72 import com.intellij.util.ui.UIUtil;
73 import com.intellij.xml.util.XmlStringUtil;
74 import org.jetbrains.annotations.NonNls;
75 import org.jetbrains.annotations.NotNull;
76 import org.jetbrains.annotations.Nullable;
77
78 import javax.swing.*;
79 import javax.swing.event.ListSelectionEvent;
80 import javax.swing.event.ListSelectionListener;
81 import javax.swing.table.TableColumn;
82 import java.awt.*;
83 import java.awt.event.*;
84 import java.util.*;
85 import java.util.List;
86 import java.util.concurrent.atomic.AtomicBoolean;
87 import java.util.concurrent.atomic.AtomicInteger;
88 import java.util.concurrent.atomic.AtomicReference;
89
90 public class ShowUsagesAction extends AnAction implements PopupAction {
91   public static final String ID = "ShowUsages";
92   private final boolean showSettingsDialogBefore;
93   public static final int USAGES_PAGE_SIZE = 100;
94
95   static final Usage MORE_USAGES_SEPARATOR = NullUsage.INSTANCE;
96   private static final UsageNode MORE_USAGES_SEPARATOR_NODE = UsageViewImpl.NULL_NODE;
97   static final Usage USAGES_OUTSIDE_SCOPE_SEPARATOR = new UsageAdapter();
98   private static final UsageNode USAGES_OUTSIDE_SCOPE_NODE = new UsageNode(USAGES_OUTSIDE_SCOPE_SEPARATOR, new UsageViewTreeModelBuilder(new UsageViewPresentation(), UsageTarget.EMPTY_ARRAY));
99
100   private static final Comparator<UsageNode> USAGE_NODE_COMPARATOR = new Comparator<UsageNode>() {
101     @Override
102     public int compare(UsageNode c1, UsageNode c2) {
103       if (c1 instanceof StringNode) return 1;
104       if (c2 instanceof StringNode) return -1;
105       Usage o1 = c1.getUsage();
106       Usage o2 = c2.getUsage();
107       int weight1 = o1 == USAGES_OUTSIDE_SCOPE_SEPARATOR ? 2 : o1 == MORE_USAGES_SEPARATOR ? 1 : 0;
108       int weight2 = o2 == USAGES_OUTSIDE_SCOPE_SEPARATOR ? 2 : o2 == MORE_USAGES_SEPARATOR ? 1 : 0;
109       if (weight1 != weight2) return weight1 - weight2;
110
111       VirtualFile v1 = UsageListCellRenderer.getVirtualFile(o1);
112       VirtualFile v2 = UsageListCellRenderer.getVirtualFile(o2);
113       String name1 = v1 == null ? null : v1.getName();
114       String name2 = v2 == null ? null : v2.getName();
115       int i = Comparing.compare(name1, name2);
116       if (i != 0) return i;
117
118       if (o1 instanceof Comparable && o2 instanceof Comparable) {
119         return ((Comparable)o1).compareTo(o2);
120       }
121
122       FileEditorLocation loc1 = o1.getLocation();
123       FileEditorLocation loc2 = o2.getLocation();
124       return Comparing.compare(loc1, loc2);
125     }
126   };
127   private static final Runnable HIDE_HINTS_ACTION = new Runnable() {
128     @Override
129     public void run() {
130       hideHints();
131     }
132   };
133   @NotNull private final UsageViewSettings myUsageViewSettings;
134   @Nullable private Runnable mySearchEverywhereRunnable;
135
136   // used from plugin.xml
137   @SuppressWarnings({"UnusedDeclaration"})
138   public ShowUsagesAction() {
139     this(false);
140   }
141
142   private ShowUsagesAction(boolean showDialogBefore) {
143     setInjectedContext(true);
144     showSettingsDialogBefore = showDialogBefore;
145
146     final UsageViewSettings usageViewSettings = UsageViewSettings.getInstance();
147     myUsageViewSettings = new UsageViewSettings();
148     myUsageViewSettings.loadState(usageViewSettings);
149     myUsageViewSettings.GROUP_BY_FILE_STRUCTURE = false;
150     myUsageViewSettings.GROUP_BY_MODULE = false;
151     myUsageViewSettings.GROUP_BY_PACKAGE = false;
152     myUsageViewSettings.GROUP_BY_USAGE_TYPE = false;
153     myUsageViewSettings.GROUP_BY_SCOPE = false;
154   }
155
156
157   @Override
158   public void actionPerformed(@NotNull AnActionEvent e) {
159     final Project project = e.getData(CommonDataKeys.PROJECT);
160     if (project == null) return;
161
162     Runnable searchEverywhere = mySearchEverywhereRunnable;
163     mySearchEverywhereRunnable = null;
164     hideHints();
165
166     if (searchEverywhere != null) {
167       searchEverywhere.run();
168       return;
169     }
170
171     final RelativePoint popupPosition = JBPopupFactory.getInstance().guessBestPopupLocation(e.getDataContext());
172     PsiDocumentManager.getInstance(project).commitAllDocuments();
173     FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.goto.usages");
174
175     UsageTarget[] usageTargets = e.getData(UsageView.USAGE_TARGETS_KEY);
176     final Editor editor = e.getData(CommonDataKeys.EDITOR);
177     if (usageTargets == null) {
178       FindUsagesAction.chooseAmbiguousTargetAndPerform(project, editor, new PsiElementProcessor<PsiElement>() {
179         @Override
180         public boolean execute(@NotNull final PsiElement element) {
181           startFindUsages(element, popupPosition, editor, USAGES_PAGE_SIZE);
182           return false;
183         }
184       });
185     }
186     else {
187       PsiElement element = ((PsiElementUsageTarget)usageTargets[0]).getElement();
188       if (element != null) {
189         startFindUsages(element, popupPosition, editor, USAGES_PAGE_SIZE);
190       }
191     }
192   }
193
194   private static void hideHints() {
195     HintManager.getInstance().hideHints(HintManager.HIDE_BY_ANY_KEY, false, false);
196   }
197
198   public void startFindUsages(@NotNull PsiElement element, @NotNull RelativePoint popupPosition, Editor editor, int maxUsages) {
199     Project project = element.getProject();
200     FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(project)).getFindUsagesManager();
201     FindUsagesHandler handler = findUsagesManager.getFindUsagesHandler(element, false);
202     if (handler == null) return;
203     if (showSettingsDialogBefore) {
204       showDialogAndFindUsages(handler, popupPosition, editor, maxUsages);
205       return;
206     }
207     showElementUsages(editor, popupPosition, handler, maxUsages, handler.getFindUsagesOptions(DataManager.getInstance().getDataContext()));
208   }
209
210   private void showElementUsages(final Editor editor,
211                                  @NotNull final RelativePoint popupPosition,
212                                  @NotNull final FindUsagesHandler handler,
213                                  final int maxUsages,
214                                  @NotNull final FindUsagesOptions options) {
215     ApplicationManager.getApplication().assertIsDispatchThread();
216     final UsageViewSettings usageViewSettings = UsageViewSettings.getInstance();
217     final UsageViewSettings savedGlobalSettings = new UsageViewSettings();
218
219     savedGlobalSettings.loadState(usageViewSettings);
220     usageViewSettings.loadState(myUsageViewSettings);
221
222     final Project project = handler.getProject();
223     UsageViewManager manager = UsageViewManager.getInstance(project);
224     FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(project)).getFindUsagesManager();
225     final UsageViewPresentation presentation = findUsagesManager.createPresentation(handler, options);
226     presentation.setDetachedMode(true);
227     final UsageViewImpl usageView = (UsageViewImpl)manager.createUsageView(UsageTarget.EMPTY_ARRAY, Usage.EMPTY_ARRAY, presentation, null);
228
229     Disposer.register(usageView, new Disposable() {
230       @Override
231       public void dispose() {
232         myUsageViewSettings.loadState(usageViewSettings);
233         usageViewSettings.loadState(savedGlobalSettings);
234       }
235     });
236     final AtomicInteger outOfScopeUsages = new AtomicInteger();
237
238     final List<Usage> usages = new ArrayList<Usage>();
239     final Set<UsageNode> visibleNodes = new LinkedHashSet<UsageNode>();
240
241     final MyTable table = new MyTable();
242     final AsyncProcessIcon processIcon = new AsyncProcessIcon("xxx");
243
244     addUsageNodes(usageView.getRoot(), usageView, new ArrayList<UsageNode>());
245
246     TableScrollingUtil.installActions(table);
247
248     final List<UsageNode> data = collectData(usages, visibleNodes, usageView, presentation);
249     setTableModel(table, usageView, data, outOfScopeUsages, options.searchScope);
250
251
252     boolean isPreviewMode = (Boolean.TRUE == PreviewManager.SERVICE.preview(handler.getProject(), UsagesPreviewPanelProvider.ID, Pair.create(usageView, table), false) );
253     Runnable itemChosenCallback = prepareTable(table, editor, popupPosition, handler, maxUsages, options, isPreviewMode);
254
255     @Nullable final JBPopup popup = isPreviewMode ? null : createUsagePopup(usages, visibleNodes, handler, editor, popupPosition,
256                                            maxUsages, usageView, options, table, itemChosenCallback, presentation, processIcon);
257     if (popup != null) {
258       Disposer.register(popup, usageView);
259
260       // show popup only if find usages takes more than 300ms, otherwise it would flicker needlessly
261       Alarm alarm = new Alarm(usageView);
262       alarm.addRequest(new Runnable() {
263         @Override
264         public void run() {
265           showPopupIfNeedTo(popup, popupPosition);
266         }
267       }, 300);
268     }
269
270     final PingEDT pingEDT = new PingEDT("Rebuild popup in EDT", new Condition<Object>() {
271       @Override
272       public boolean value(Object o) {
273         return popup != null && popup.isDisposed();
274       }
275     }, 100, new Runnable() {
276       @Override
277       public void run() {
278         if (popup != null && popup.isDisposed()) return;
279
280         final List<UsageNode> nodes = new ArrayList<UsageNode>();
281         List<Usage> copy;
282         synchronized (usages) {
283           // open up popup as soon as several usages 've been found
284           if (popup != null && (!popup.isVisible() && (usages.size() <= 1 || !showPopupIfNeedTo(popup, popupPosition)))) {
285             return;
286           }
287           addUsageNodes(usageView.getRoot(), usageView, nodes);
288           copy = new ArrayList<Usage>(usages);
289         }
290
291         rebuildTable(usageView, copy, nodes, table, popup, presentation, popupPosition, !processIcon.isDisposed(), outOfScopeUsages,
292                      options.searchScope);
293       }
294     });
295
296     final MessageBusConnection messageBusConnection = project.getMessageBus().connect(usageView);
297     messageBusConnection.subscribe(UsageFilteringRuleProvider.RULES_CHANGED, new Runnable() {
298       @Override
299       public void run() {
300         pingEDT.ping();
301       }
302     });
303
304     final UsageTarget[] myUsageTarget = {new PsiElement2UsageTargetAdapter(handler.getPsiElement())};
305     Processor<Usage> collect = new Processor<Usage>() {
306       @Override
307       public boolean process(@NotNull final Usage usage) {
308         if (!UsageViewManagerImpl.isInScope(usage, options.searchScope)) {
309           if (outOfScopeUsages.getAndIncrement() == 0) {
310             visibleNodes.add(USAGES_OUTSIDE_SCOPE_NODE);
311             usages.add(USAGES_OUTSIDE_SCOPE_SEPARATOR);
312           }
313           return true;
314         }
315         synchronized (usages) {
316           if (visibleNodes.size() >= maxUsages) return false;
317           if(UsageViewManager.isSelfUsage(usage, myUsageTarget)) return true;
318           UsageNode node = ApplicationManager.getApplication().runReadAction(new Computable<UsageNode>() {
319             @Override
320             public UsageNode compute() {
321               return usageView.doAppendUsage(usage);
322             }
323           });
324           usages.add(usage);
325           if (node != null) {
326             visibleNodes.add(node);
327             boolean continueSearch = true;
328             if (visibleNodes.size() == maxUsages) {
329               visibleNodes.add(MORE_USAGES_SEPARATOR_NODE);
330               usages.add(MORE_USAGES_SEPARATOR);
331               continueSearch = false;
332             }
333             pingEDT.ping();
334
335             return continueSearch;
336           }
337         }
338
339         return true;
340       }
341     };
342
343     final ProgressIndicator indicator = FindUsagesManager.startProcessUsages(handler, handler.getPrimaryElements(), handler.getSecondaryElements(), collect, options, new Runnable() {
344       @Override
345       public void run() {
346         ApplicationManager.getApplication().invokeLater(new Runnable() {
347           @Override
348           public void run() {
349             Disposer.dispose(processIcon);
350             Container parent = processIcon.getParent();
351             if (parent != null) {
352               parent.remove(processIcon);
353               parent.repaint();
354             }
355             pingEDT.ping(); // repaint title
356             synchronized (usages) {
357               if (visibleNodes.isEmpty()) {
358                 if (usages.isEmpty()) {
359                   String text = UsageViewBundle.message("no.usages.found.in", searchScopePresentableName(options));
360                   hint(editor, text, handler, popupPosition, maxUsages, options, false);
361                   cancel(popup);
362                 }
363                 else {
364                   // all usages filtered out
365                 }
366               }
367               else if (visibleNodes.size() == 1) {
368                 if (usages.size() == 1) {
369                   //the only usage
370                   Usage usage = visibleNodes.iterator().next().getUsage();
371                   if (usage == USAGES_OUTSIDE_SCOPE_SEPARATOR) {
372                     hint(editor, UsageViewManagerImpl.outOfScopeMessage(outOfScopeUsages.get(), options.searchScope), handler, popupPosition, maxUsages, options, true);
373                   }
374                   else {
375                     String message = UsageViewBundle.message("show.usages.only.usage", searchScopePresentableName(options));
376                     navigateAndHint(usage, message, handler, popupPosition, maxUsages, options);
377                   }
378                   cancel(popup);
379                 }
380                 else {
381                   assert usages.size() > 1 : usages;
382                   // usage view can filter usages down to one
383                   Usage visibleUsage = visibleNodes.iterator().next().getUsage();
384                   if (areAllUsagesInOneLine(visibleUsage, usages)) {
385                     String hint = UsageViewBundle.message("all.usages.are.in.this.line", usages.size(), searchScopePresentableName(options));
386                     navigateAndHint(visibleUsage, hint, handler, popupPosition, maxUsages, options);
387                     cancel(popup);
388                   }
389                 }
390               }
391               else {
392                 if (popup != null) {
393                   String title = presentation.getTabText();
394                   boolean shouldShowMoreSeparator = visibleNodes.contains(MORE_USAGES_SEPARATOR_NODE);
395                   String fullTitle =
396                     getFullTitle(usages, title, shouldShowMoreSeparator, visibleNodes.size() - (shouldShowMoreSeparator ? 1 : 0), false);
397                   ((AbstractPopup)popup).setCaption(fullTitle);
398                 }
399               }
400             }
401           }
402         }, project.getDisposed());
403       }
404     });
405     if (popup != null) {
406       Disposer.register(popup, new Disposable() {
407         @Override
408         public void dispose() {
409           indicator.cancel();
410         }
411       });
412     }
413   }
414
415   @NotNull
416   private static UsageNode createStringNode(@NotNull final Object string) {
417     return new StringNode(string);
418   }
419
420   private static class MyModel extends ListTableModel<UsageNode> implements ModelDiff.Model<Object> {
421     private MyModel(@NotNull List<UsageNode> data, int cols) {
422       super(cols(cols), data, 0);
423     }
424
425     @NotNull
426     private static ColumnInfo[] cols(int cols) {
427       ColumnInfo<UsageNode, UsageNode> o = new ColumnInfo<UsageNode, UsageNode>("") {
428         @Nullable
429         @Override
430         public UsageNode valueOf(UsageNode node) {
431           return node;
432         }
433       };
434       List<ColumnInfo<UsageNode, UsageNode>> list = Collections.nCopies(cols, o);
435       return list.toArray(new ColumnInfo[list.size()]);
436     }
437
438     @Override
439     public void addToModel(int idx, Object element) {
440       UsageNode node = element instanceof UsageNode ? (UsageNode)element : createStringNode(element);
441
442       if (idx < getRowCount()) {
443         insertRow(idx, node);
444       }
445       else {
446         addRow(node);
447       }
448     }
449
450     @Override
451     public void removeRangeFromModel(int start, int end) {
452       for (int i=end; i>=start; i--) {
453         removeRow(i);
454       }
455     }
456   }
457
458   private static boolean showPopupIfNeedTo(@NotNull JBPopup popup, @NotNull RelativePoint popupPosition) {
459     if (!popup.isDisposed() && !popup.isVisible()) {
460       popup.show(popupPosition);
461       return true;
462     }
463     else {
464       return false;
465     }
466   }
467
468
469   @NotNull
470   private JComponent createHintComponent(@NotNull String text,
471                                          @NotNull final FindUsagesHandler handler,
472                                          @NotNull final RelativePoint popupPosition,
473                                          final Editor editor,
474                                          @NotNull final Runnable cancelAction,
475                                          final int maxUsages,
476                                          @NotNull final FindUsagesOptions options,
477                                          boolean isWarning) {
478     JComponent label = HintUtil.createInformationLabel(suggestSecondInvocation(options, handler, text + "&nbsp;"));
479     if (isWarning) {
480       label.setBackground(MessageType.WARNING.getPopupBackground());
481     }
482     InplaceButton button = createSettingsButton(handler, popupPosition, editor, maxUsages, cancelAction);
483
484     JPanel panel = new JPanel(new BorderLayout()) {
485       @Override
486       public void addNotify() {
487         mySearchEverywhereRunnable = new Runnable() {
488           @Override
489           public void run() {
490             searchEverywhere(options, handler, editor, popupPosition, maxUsages);
491           }
492         };
493         super.addNotify();
494       }
495
496       @Override
497       public void removeNotify() {
498         mySearchEverywhereRunnable = null;
499         super.removeNotify();
500       }
501     };
502     button.setBackground(label.getBackground());
503     panel.setBackground(label.getBackground());
504     label.setOpaque(false);
505     label.setBorder(null);
506     panel.setBorder(HintUtil.createHintBorder());
507     panel.add(label, BorderLayout.CENTER);
508     panel.add(button, BorderLayout.EAST);
509     return panel;
510   }
511
512   @NotNull
513   private InplaceButton createSettingsButton(@NotNull final FindUsagesHandler handler,
514                                              @NotNull final RelativePoint popupPosition,
515                                              final Editor editor,
516                                              final int maxUsages,
517                                              @NotNull final Runnable cancelAction) {
518     String shortcutText = "";
519     KeyboardShortcut shortcut = UsageViewImpl.getShowUsagesWithSettingsShortcut();
520     if (shortcut != null) {
521       shortcutText = "(" + KeymapUtil.getShortcutText(shortcut) + ")";
522     }
523     return new InplaceButton("Settings..." + shortcutText, AllIcons.General.Settings, new ActionListener() {
524       @Override
525       public void actionPerformed(ActionEvent e) {
526         SwingUtilities.invokeLater(new Runnable() {
527           @Override
528           public void run() {
529             showDialogAndFindUsages(handler, popupPosition, editor, maxUsages);
530           }
531         });
532         cancelAction.run();
533       }
534     });
535   }
536
537   private void showDialogAndFindUsages(@NotNull FindUsagesHandler handler,
538                                        @NotNull RelativePoint popupPosition,
539                                        Editor editor,
540                                        int maxUsages) {
541     AbstractFindUsagesDialog dialog = handler.getFindUsagesDialog(false, false, false);
542     if (dialog.showAndGet()) {
543       dialog.calcFindUsagesOptions();
544       FindUsagesOptions options = handler.getFindUsagesOptions(DataManager.getInstance().getDataContext());
545       showElementUsages(editor, popupPosition, handler, maxUsages, options);
546     }
547   }
548
549   @NotNull
550   private static String searchScopePresentableName(@NotNull FindUsagesOptions options) {
551     return options.searchScope.getDisplayName();
552   }
553
554   @NotNull
555   private Runnable prepareTable(final MyTable table,
556                                 final Editor editor,
557                                 final RelativePoint popupPosition,
558                                 final FindUsagesHandler handler,
559                                 final int maxUsages,
560                                 @NotNull final FindUsagesOptions options,
561                                 final boolean previewMode) {
562
563     SpeedSearchBase<JTable> speedSearch = new MySpeedSearch(table);
564     speedSearch.setComparator(new SpeedSearchComparator(false));
565
566     table.setRowHeight(PlatformIcons.CLASS_ICON.getIconHeight()+2);
567     table.setShowGrid(false);
568     table.setShowVerticalLines(false);
569     table.setShowHorizontalLines(false);
570     table.setTableHeader(null);
571     table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
572     table.setIntercellSpacing(new Dimension(0, 0));
573     final AtomicReference<Object> selectedUsage = new AtomicReference<Object>();
574     final AtomicBoolean moreUsagesSelected = new AtomicBoolean();
575     final AtomicBoolean outsideScopeUsagesSelected = new AtomicBoolean();
576     final Runnable itemChosenCallback = new Runnable() {
577       @Override
578       public void run() {
579         if (moreUsagesSelected.get()) {
580           appendMoreUsages(editor, popupPosition, handler, maxUsages, options);
581           return;
582         }
583         if (outsideScopeUsagesSelected.get()) {
584           options.searchScope = GlobalSearchScope.projectScope(handler.getProject());
585           showElementUsages(editor, popupPosition, handler, maxUsages, options);
586           return;
587         }
588         Object usage = selectedUsage.get();
589         if (usage instanceof UsageInfo) {
590           UsageViewUtil.navigateTo((UsageInfo)usage, true);
591         }
592         else if (usage instanceof Navigatable) {
593           ((Navigatable)usage).navigate(true);
594         }
595       }
596     };
597     table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
598       @Override
599       public void valueChanged(ListSelectionEvent e) {
600         selectedUsage.set(null);
601         int[] selected = table.getSelectedRows();
602         for (int i : selected) {
603           Object value = table.getValueAt(i, 0);
604           if (value instanceof UsageNode) {
605             Usage usage = ((UsageNode)value).getUsage();
606             outsideScopeUsagesSelected.set(false);
607             moreUsagesSelected.set(false);
608             if (usage == USAGES_OUTSIDE_SCOPE_SEPARATOR) {
609               outsideScopeUsagesSelected.set(true);
610               selectedUsage.set(null);
611             }
612             else if (usage == MORE_USAGES_SEPARATOR) {
613               moreUsagesSelected.set(true);
614               selectedUsage.set(null);
615             }
616             else {
617               selectedUsage.set(usage instanceof UsageInfo2UsageAdapter ? ((UsageInfo2UsageAdapter)usage).getUsageInfo().copy() : usage);
618             }
619             break;
620           }
621         }
622       }
623     });
624     if (previewMode) {
625       table.addMouseListener(new MouseAdapter() {
626         @Override
627         public void mouseReleased(MouseEvent e) {
628           if (UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED) && !UIUtil.isSelectionButtonDown(e) && !e.isConsumed()) {
629             itemChosenCallback.run();
630           }
631         }
632       });
633       table.addKeyListener(new KeyAdapter() {
634         @Override
635         public void keyPressed(KeyEvent e) {
636           if (e.getKeyCode() == KeyEvent.VK_ENTER) {
637             itemChosenCallback.run();
638           }
639         }
640       });
641     }
642     return itemChosenCallback;
643   }
644
645   @NotNull
646   private JBPopup createUsagePopup(@NotNull final List<Usage> usages,
647                                    @NotNull Set<UsageNode> visibleNodes,
648                                    @NotNull final FindUsagesHandler handler,
649                                    final Editor editor,
650                                    @NotNull final RelativePoint popupPosition,
651                                    final int maxUsages,
652                                    @NotNull final UsageViewImpl usageView,
653                                    @NotNull final FindUsagesOptions options,
654                                    @NotNull final JTable table,
655                                    @NotNull final Runnable itemChoseCallback,
656                                    @NotNull final UsageViewPresentation presentation,
657                                    @NotNull final AsyncProcessIcon processIcon) {
658
659     PopupChooserBuilder builder = new PopupChooserBuilder(table);
660     final String title = presentation.getTabText();
661     if (title != null) {
662       String result = getFullTitle(usages, title, false, visibleNodes.size() - 1, true);
663       builder.setTitle(result);
664       builder.setAdText(getSecondInvocationTitle(options, handler));
665     }
666
667     builder.setMovable(true).setResizable(true);
668     builder.setMovable(true).setResizable(true);
669     builder.setItemChoosenCallback(itemChoseCallback);
670     final JBPopup[] popup = new JBPopup[1];
671
672     KeyboardShortcut shortcut = UsageViewImpl.getShowUsagesWithSettingsShortcut();
673     if (shortcut != null) {
674       new DumbAwareAction() {
675         @Override
676         public void actionPerformed(@NotNull AnActionEvent e) {
677           cancel(popup);
678           showDialogAndFindUsages(handler, popupPosition, editor, maxUsages);
679         }
680       }.registerCustomShortcutSet(new CustomShortcutSet(shortcut.getFirstKeyStroke()), table);
681     }
682     shortcut = getShowUsagesShortcut();
683     if (shortcut != null) {
684       new DumbAwareAction() {
685         @Override
686         public void actionPerformed(@NotNull AnActionEvent e) {
687           cancel(popup);
688           searchEverywhere(options, handler, editor, popupPosition, maxUsages);
689         }
690       }.registerCustomShortcutSet(new CustomShortcutSet(shortcut.getFirstKeyStroke()), table);
691     }
692
693     InplaceButton settingsButton = createSettingsButton(handler, popupPosition, editor, maxUsages, new Runnable() {
694       @Override
695       public void run() {
696         cancel(popup);
697       }
698     });
699
700     ActiveComponent spinningProgress = new ActiveComponent() {
701       @Override
702       public void setActive(boolean active) {
703       }
704
705       @Override
706       public JComponent getComponent() {
707         return processIcon;
708       }
709     };
710     final DefaultActionGroup pinGroup = new DefaultActionGroup();
711     final ActiveComponent pin = createPinButton(handler, usageView, options, popup, pinGroup);
712     builder.setCommandButton(new CompositeActiveComponent(spinningProgress, settingsButton, pin));
713
714     DefaultActionGroup toolbar = new DefaultActionGroup();
715     usageView.addFilteringActions(toolbar);
716
717     toolbar.add(UsageGroupingRuleProviderImpl.createGroupByFileStructureAction(usageView)); 
718     ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, toolbar, true);
719     actionToolbar.setReservePlaceAutoPopupIcon(false);
720     final JComponent toolBar = actionToolbar.getComponent();
721     toolBar.setOpaque(false);
722     builder.setSettingButton(toolBar);
723
724     popup[0] = builder.createPopup();
725     JComponent content = popup[0].getContent();
726
727     myWidth = (int)(toolBar.getPreferredSize().getWidth()
728                   + new JLabel(getFullTitle(usages, title, false, visibleNodes.size() - 1, true)).getPreferredSize().getWidth()
729                   + settingsButton.getPreferredSize().getWidth());
730     myWidth = -1;
731     for (AnAction action : toolbar.getChildren(null)) {
732       action.unregisterCustomShortcutSet(usageView.getComponent());
733       action.registerCustomShortcutSet(action.getShortcutSet(), content);
734     }
735
736     for (AnAction action : pinGroup.getChildren(null)) {
737       action.unregisterCustomShortcutSet(usageView.getComponent());
738       action.registerCustomShortcutSet(action.getShortcutSet(), content);
739     }
740
741     return popup[0];
742   }
743
744   private ActiveComponent createPinButton(@NotNull final FindUsagesHandler handler,
745                                           @NotNull final UsageViewImpl usageView,
746                                           @NotNull final FindUsagesOptions options,
747                                           @NotNull final JBPopup[] popup,
748                                           @NotNull DefaultActionGroup pinGroup) {
749     final AnAction pinAction =
750       new AnAction("Open Find Usages Toolwindow", "Show all usages in a separate toolwindow", AllIcons.General.AutohideOff) {
751         {
752           AnAction action = ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_USAGES);
753           setShortcutSet(action.getShortcutSet());
754         }
755
756         @Override
757         public void actionPerformed(@NotNull AnActionEvent e) {
758           hideHints();
759           cancel(popup);
760           FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(usageView.getProject())).getFindUsagesManager();
761           findUsagesManager.findUsages(handler.getPrimaryElements(), handler.getSecondaryElements(), handler, options,
762                                        FindSettings.getInstance().isSkipResultsWithOneUsage());
763         }
764       };
765     pinGroup.add(pinAction);
766     final ActionToolbar pinToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, pinGroup, true);
767     pinToolbar.setReservePlaceAutoPopupIcon(false);
768     final JComponent pinToolBar = pinToolbar.getComponent();
769     pinToolBar.setBorder(null);
770     pinToolBar.setOpaque(false);
771
772     return new ActiveComponent() {
773       @Override
774       public void setActive(boolean active) {
775       }
776
777       @Override
778       public JComponent getComponent() {
779         return pinToolBar;
780       }
781     };
782   }
783
784   private static void cancel(@Nullable JBPopup... popup) {
785     if (popup != null && popup.length>0 && popup[0] != null) {
786       popup[0].cancel();
787     }
788   }
789
790   @NotNull
791   private static String getFullTitle(@NotNull List<Usage> usages,
792                                      @NotNull String title,
793                                      boolean hadMoreSeparator,
794                                      int visibleNodesCount,
795                                      boolean findUsagesInProgress) {
796     String s;
797     if (hadMoreSeparator) {
798       s = "<b>Some</b> " + title + " " + "<b>(Only " + visibleNodesCount + " usages shown" +(findUsagesInProgress ? " so far" : "")+")</b>";
799     }
800     else {
801       s = title + " (" + UsageViewBundle.message("usages.n", usages.size()) + (findUsagesInProgress ? " so far" : "") + ")";
802     }
803     return "<html><nobr>" + s + "</nobr></html>";
804   }
805
806   @NotNull
807   private static String suggestSecondInvocation(@NotNull FindUsagesOptions options, @NotNull FindUsagesHandler handler, @NotNull String text) {
808     final String title = getSecondInvocationTitle(options, handler);
809
810     if (title != null) {
811         text += "<br><small> " + title + "</small>";
812     }
813     return XmlStringUtil.wrapInHtml(text);
814   }
815
816   @Nullable
817   private static String getSecondInvocationTitle(@NotNull FindUsagesOptions options, @NotNull FindUsagesHandler handler) {
818     if (getShowUsagesShortcut() != null) {
819        GlobalSearchScope maximalScope = FindUsagesManager.getMaximalScope(handler);
820       if (!options.searchScope.equals(maximalScope)) {
821          return "Press " + KeymapUtil.getShortcutText(getShowUsagesShortcut()) + " again to search in " + maximalScope.getDisplayName();
822        }
823      }
824      return null;
825   }
826
827   private void searchEverywhere(@NotNull FindUsagesOptions options,
828                                 @NotNull FindUsagesHandler handler,
829                                 Editor editor,
830                                 @NotNull RelativePoint popupPosition,
831                                 int maxUsages) {
832     FindUsagesOptions cloned = options.clone();
833     cloned.searchScope = FindUsagesManager.getMaximalScope(handler);
834     showElementUsages(editor, popupPosition, handler, maxUsages, cloned);
835   }
836
837   @Nullable
838   private static KeyboardShortcut getShowUsagesShortcut() {
839     return ActionManager.getInstance().getKeyboardShortcut(ID);
840   }
841
842   private static int filtered(@NotNull List<Usage> usages, @NotNull UsageViewImpl usageView) {
843     int count=0;
844     for (Usage usage : usages) {
845       if (!usageView.isVisible(usage)) count++;
846     }
847     return count;
848   }
849
850   private static int getUsageOffset(@NotNull Usage usage) {
851     if (!(usage instanceof UsageInfo2UsageAdapter)) return -1;
852     PsiElement element = ((UsageInfo2UsageAdapter)usage).getElement();
853     if (element == null) return -1;
854     return element.getTextRange().getStartOffset();
855   }
856
857   private static boolean areAllUsagesInOneLine(@NotNull Usage visibleUsage, @NotNull List<Usage> usages) {
858     Editor editor = getEditorFor(visibleUsage);
859     if (editor == null) return false;
860     int offset = getUsageOffset(visibleUsage);
861     if (offset == -1) return false;
862     int lineNumber = editor.getDocument().getLineNumber(offset);
863     for (Usage other : usages) {
864       Editor otherEditor = getEditorFor(other);
865       if (otherEditor != editor) return false;
866       int otherOffset = getUsageOffset(other);
867       if (otherOffset == -1) return false;
868       int otherLine = otherEditor.getDocument().getLineNumber(otherOffset);
869       if (otherLine != lineNumber) return false;
870     }
871     return true;
872   }
873
874   @NotNull
875   private static MyModel setTableModel(@NotNull JTable table,
876                                        @NotNull UsageViewImpl usageView,
877                                        @NotNull final List<UsageNode> data,
878                                        @NotNull AtomicInteger outOfScopeUsages,
879                                        @NotNull SearchScope searchScope) {
880     ApplicationManager.getApplication().assertIsDispatchThread();
881     final int columnCount = calcColumnCount(data);
882     MyModel model = table.getModel() instanceof MyModel ? (MyModel)table.getModel() : null;
883     if (model == null || model.getColumnCount() != columnCount) {
884       model = new MyModel(data, columnCount);
885       table.setModel(model);
886
887       ShowUsagesTableCellRenderer renderer = new ShowUsagesTableCellRenderer(usageView, outOfScopeUsages, searchScope);
888       for (int i=0;i<table.getColumnModel().getColumnCount();i++) {
889         TableColumn column = table.getColumnModel().getColumn(i);
890         column.setCellRenderer(renderer);
891       }
892     }
893     return model;
894   }
895
896   private static int calcColumnCount(@NotNull List<UsageNode> data) {
897     return data.isEmpty() || data.get(0) instanceof StringNode ? 1 : 3;
898   }
899
900   @NotNull
901   private static List<UsageNode> collectData(@NotNull List<Usage> usages,
902                                              @NotNull Collection<UsageNode> visibleNodes,
903                                              @NotNull UsageViewImpl usageView,
904                                              @NotNull UsageViewPresentation presentation) {
905     @NotNull List<UsageNode> data = new ArrayList<UsageNode>();
906     int filtered = filtered(usages, usageView);
907     if (filtered != 0) {
908       data.add(createStringNode(UsageViewBundle.message("usages.were.filtered.out", filtered)));
909     }
910     data.addAll(visibleNodes);
911     if (data.isEmpty()) {
912       String progressText = StringUtil.escapeXml(UsageViewManagerImpl.getProgressTitle(presentation));
913       data.add(createStringNode(progressText));
914     }
915     Collections.sort(data, USAGE_NODE_COMPARATOR);
916     return data;
917   }
918
919   private static int calcMaxWidth(JTable table) {
920     int colsNum = table.getColumnModel().getColumnCount();
921
922     int totalWidth = 0;
923     for (int col = 0; col < colsNum -1; col++) {
924       TableColumn column = table.getColumnModel().getColumn(col);
925       int preferred = column.getPreferredWidth();
926       int width = Math.max(preferred, columnMaxWidth(table, col));
927       totalWidth += width;
928       column.setMinWidth(width);
929       column.setMaxWidth(width);
930       column.setWidth(width);
931       column.setPreferredWidth(width);
932     }
933
934     totalWidth += columnMaxWidth(table, colsNum - 1);
935
936     return totalWidth;
937   }
938
939   private static int columnMaxWidth(@NotNull JTable table, int col) {
940     TableColumn column = table.getColumnModel().getColumn(col);
941     int width = 0;
942     for (int row = 0; row < table.getRowCount(); row++) {
943       Component component = table.prepareRenderer(column.getCellRenderer(), row, col);
944
945       int rendererWidth = component.getPreferredSize().width;
946       width = Math.max(width, rendererWidth + table.getIntercellSpacing().width);
947     }
948     return width;
949   }
950
951   private int myWidth;
952
953   private void rebuildTable(@NotNull final UsageViewImpl usageView,
954                             @NotNull final List<Usage> usages,
955                             @NotNull List<UsageNode> nodes,
956                             @NotNull final JTable table,
957                             @Nullable final JBPopup popup,
958                             @NotNull final UsageViewPresentation presentation,
959                             @NotNull final RelativePoint popupPosition,
960                             boolean findUsagesInProgress,
961                             @NotNull AtomicInteger outOfScopeUsages,
962                             @NotNull SearchScope searchScope) {
963     ApplicationManager.getApplication().assertIsDispatchThread();
964
965     boolean shouldShowMoreSeparator = usages.contains(MORE_USAGES_SEPARATOR);
966     if (shouldShowMoreSeparator) {
967       nodes.add(MORE_USAGES_SEPARATOR_NODE);
968     }
969     boolean hasOutsideScopeUsages = usages.contains(USAGES_OUTSIDE_SCOPE_SEPARATOR);
970     if (hasOutsideScopeUsages && !shouldShowMoreSeparator) {
971       nodes.add(USAGES_OUTSIDE_SCOPE_NODE);
972     }
973
974     String title = presentation.getTabText();
975     String fullTitle = getFullTitle(usages, title, shouldShowMoreSeparator || hasOutsideScopeUsages, nodes.size() - (shouldShowMoreSeparator || hasOutsideScopeUsages ? 1 : 0), findUsagesInProgress);
976     if (popup != null) {
977       ((AbstractPopup)popup).setCaption(fullTitle);
978     }
979
980     List<UsageNode> data = collectData(usages, nodes, usageView, presentation);
981     MyModel tableModel = setTableModel(table, usageView, data, outOfScopeUsages, searchScope);
982     List<UsageNode> existingData = tableModel.getItems();
983
984     int row = table.getSelectedRow();
985
986     int newSelection = updateModel(tableModel, existingData, data, row == -1 ? 0 : row);
987     if (newSelection < 0 || newSelection >= tableModel.getRowCount()) {
988       TableScrollingUtil.ensureSelectionExists(table);
989       newSelection = table.getSelectedRow();
990     }
991     else {
992       table.getSelectionModel().setSelectionInterval(newSelection, newSelection);
993     }
994     TableScrollingUtil.ensureIndexIsVisible(table, newSelection, 0);
995
996     if (popup != null) {
997       setSizeAndDimensions(table, popup, popupPosition, data);
998     }
999   }
1000
1001   // returns new selection
1002   private static int updateModel(@NotNull MyModel tableModel, @NotNull List<UsageNode> listOld, @NotNull List<UsageNode> listNew, int oldSelection) {
1003     UsageNode[] oa = listOld.toArray(new UsageNode[listOld.size()]);
1004     UsageNode[] na = listNew.toArray(new UsageNode[listNew.size()]);
1005     List<ModelDiff.Cmd> cmds = ModelDiff.createDiffCmds(tableModel, oa, na);
1006     int selection = oldSelection;
1007     if (cmds != null) {
1008       for (ModelDiff.Cmd cmd : cmds) {
1009         selection = cmd.translateSelection(selection);
1010         cmd.apply();
1011       }
1012     }
1013     return selection;
1014   }
1015
1016   private void setSizeAndDimensions(@NotNull JTable table,
1017                                     @NotNull JBPopup popup,
1018                                     @NotNull RelativePoint popupPosition,
1019                                     @NotNull List<UsageNode> data) {
1020     JComponent content = popup.getContent();
1021     Window window = SwingUtilities.windowForComponent(content);
1022     Dimension d = window.getSize();
1023
1024     int width = calcMaxWidth(table);
1025     width = (int)Math.max(d.getWidth(), width);
1026     Dimension headerSize = ((AbstractPopup)popup).getHeaderPreferredSize();
1027     width = Math.max((int)headerSize.getWidth(), width);
1028     width = Math.max(myWidth, width);
1029
1030     if (myWidth == -1) myWidth = width;
1031     int newWidth = Math.max(width, d.width + width - myWidth);
1032
1033     myWidth = newWidth;
1034
1035     int rowsToShow = Math.min(30, data.size());
1036     Dimension dimension = new Dimension(newWidth, table.getRowHeight() * rowsToShow);
1037     Rectangle rectangle = fitToScreen(dimension, popupPosition, table);
1038     if (!data.isEmpty()) {
1039       TableScrollingUtil.ensureSelectionExists(table);
1040     }
1041     table.setSize(rectangle.getSize());
1042     //table.setPreferredSize(dimension);
1043     //table.setMaximumSize(dimension);
1044     //table.setPreferredScrollableViewportSize(dimension);
1045
1046
1047     Dimension footerSize = ((AbstractPopup)popup).getFooterPreferredSize();
1048
1049     rectangle.height += headerSize.height + footerSize.height + 4/* invisible borders, margins etc*/;
1050     ScreenUtil.fitToScreen(rectangle);
1051     Dimension newDim = rectangle.getSize();
1052     window.setBounds(rectangle);
1053     window.setMinimumSize(newDim);
1054     window.setMaximumSize(newDim);
1055
1056     window.validate();
1057     window.repaint();
1058     table.revalidate();
1059     table.repaint();
1060   }
1061
1062   private static Rectangle fitToScreen(@NotNull Dimension newDim, @NotNull RelativePoint popupPosition, JTable table) {
1063     Rectangle rectangle = new Rectangle(popupPosition.getScreenPoint(), newDim);
1064     ScreenUtil.fitToScreen(rectangle);
1065     if (rectangle.getHeight() != newDim.getHeight()) {
1066       int newHeight = (int)rectangle.getHeight();
1067       int roundedHeight = newHeight - newHeight % table.getRowHeight();
1068       rectangle.setSize((int)rectangle.getWidth(), Math.max(roundedHeight, table.getRowHeight()));
1069     }
1070     return rectangle;
1071
1072   }
1073
1074   private void appendMoreUsages(Editor editor,
1075                                 @NotNull RelativePoint popupPosition,
1076                                 @NotNull FindUsagesHandler handler,
1077                                 int maxUsages,
1078                                 @NotNull FindUsagesOptions options) {
1079     showElementUsages(editor, popupPosition, handler, maxUsages+USAGES_PAGE_SIZE, options);
1080   }
1081
1082   private static void addUsageNodes(@NotNull GroupNode root, @NotNull final UsageViewImpl usageView, @NotNull List<UsageNode> outNodes) {
1083     for (UsageNode node : root.getUsageNodes()) {
1084       Usage usage = node.getUsage();
1085       if (usageView.isVisible(usage)) {
1086         node.setParent(root);
1087         outNodes.add(node);
1088       }
1089     }
1090     for (GroupNode groupNode : root.getSubGroups()) {
1091       groupNode.setParent(root);
1092       addUsageNodes(groupNode, usageView, outNodes);
1093     }
1094   }
1095
1096   @Override
1097   public void update(@NotNull AnActionEvent e){
1098     FindUsagesInFileAction.updateFindUsagesAction(e);
1099   }
1100
1101   private void navigateAndHint(@NotNull Usage usage,
1102                                @Nullable final String hint,
1103                                @NotNull final FindUsagesHandler handler,
1104                                @NotNull final RelativePoint popupPosition,
1105                                final int maxUsages,
1106                                @NotNull final FindUsagesOptions options) {
1107     usage.navigate(true);
1108     if (hint == null) return;
1109     final Editor newEditor = getEditorFor(usage);
1110     if (newEditor == null) return;
1111     hint(newEditor, hint, handler, popupPosition, maxUsages, options, false);
1112   }
1113
1114   private void showHint(@Nullable final Editor editor,
1115                         @NotNull String hint,
1116                         @NotNull FindUsagesHandler handler,
1117                         @NotNull final RelativePoint popupPosition,
1118                         int maxUsages,
1119                         @NotNull FindUsagesOptions options,
1120                         boolean isWarning) {
1121     JComponent label = createHintComponent(hint, handler, popupPosition, editor, HIDE_HINTS_ACTION, maxUsages, options, isWarning);
1122     if (editor == null || editor.isDisposed() || !editor.getComponent().isShowing()) {
1123       HintManager.getInstance().showHint(label, popupPosition, HintManager.HIDE_BY_ANY_KEY |
1124                                                                HintManager.HIDE_BY_TEXT_CHANGE | HintManager.HIDE_BY_SCROLLING, 0);
1125     }
1126     else {
1127       HintManager.getInstance().showInformationHint(editor, label);
1128     }
1129   }
1130
1131   private void hint(@Nullable final Editor editor,
1132                     @NotNull final String hint,
1133                     @NotNull final FindUsagesHandler handler,
1134                     @NotNull final RelativePoint popupPosition,
1135                     final int maxUsages,
1136                     @NotNull final FindUsagesOptions options,
1137                     final boolean isWarning) {
1138     final Project project = handler.getProject();
1139     //opening editor is performing in invokeLater
1140     IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(new Runnable() {
1141       @Override
1142       public void run() {
1143         Runnable runnable = new Runnable() {
1144           @Override
1145           public void run() {
1146             // after new editor created, some editor resizing events are still bubbling. To prevent hiding hint, invokeLater this
1147             IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(new Runnable() {
1148               @Override
1149               public void run() {
1150                 showHint(editor, hint, handler, popupPosition, maxUsages, options, isWarning);
1151               }
1152             });
1153           }
1154         };
1155         if (editor == null) runnable.run();
1156         else editor.getScrollingModel().runActionOnScrollingFinished(runnable);
1157       }
1158     });
1159   }
1160
1161   @Nullable
1162   private static Editor getEditorFor(@NotNull Usage usage) {
1163     FileEditorLocation location = usage.getLocation();
1164     FileEditor newFileEditor = location == null ? null : location.getEditor();
1165     return newFileEditor instanceof TextEditor ? ((TextEditor)newFileEditor).getEditor() : null;
1166   }
1167
1168   private static class MyTable extends JBTableWithHintProvider implements DataProvider {
1169     @Override
1170     public boolean getScrollableTracksViewportWidth() {
1171       return true;
1172     }
1173
1174     @Override
1175     public Object getData(@NonNls String dataId) {
1176       if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
1177         final int[] selected = getSelectedRows();
1178         if (selected.length == 1) {
1179           return getPsiElementForHint(getValueAt(selected[0], 0));
1180         }
1181       }
1182       return null;
1183     }
1184
1185     @Override
1186     @Nullable
1187     protected PsiElement getPsiElementForHint(Object selectedValue) {
1188       if (selectedValue instanceof UsageNode) {
1189         final Usage usage = ((UsageNode)selectedValue).getUsage();
1190         if (usage instanceof UsageInfo2UsageAdapter) {
1191           final PsiElement element = ((UsageInfo2UsageAdapter)usage).getElement();
1192           if (element != null) {
1193             final PsiElement view = UsageToPsiElementProvider.findAppropriateParentFrom(element);
1194             return view == null ? element : view;
1195           }
1196         }
1197       }
1198       return null;
1199     }
1200   }
1201
1202   static class StringNode extends UsageNode {
1203     @NotNull private final Object myString;
1204
1205     public StringNode(@NotNull Object string) {
1206       super(NullUsage.INSTANCE, new UsageViewTreeModelBuilder(new UsageViewPresentation(), UsageTarget.EMPTY_ARRAY));
1207       myString = string;
1208     }
1209
1210     @Override
1211     public String toString() {
1212       return myString.toString();
1213     }
1214   }
1215
1216   private static class MySpeedSearch extends SpeedSearchBase<JTable> {
1217     public MySpeedSearch(@NotNull MyTable table) {
1218       super(table);
1219     }
1220
1221     @Override
1222     protected int getSelectedIndex() {
1223       return getTable().getSelectedRow();
1224     }
1225
1226     @Override
1227     protected int convertIndexToModel(int viewIndex) {
1228       return getTable().convertRowIndexToModel(viewIndex);
1229     }
1230
1231     @NotNull
1232     @Override
1233     protected Object[] getAllElements() {
1234       return ((MyModel)getTable().getModel()).getItems().toArray();
1235     }
1236
1237     @Override
1238     protected String getElementText(@NotNull Object element) {
1239       if (!(element instanceof UsageNode)) return element.toString();
1240       UsageNode node = (UsageNode)element;
1241       if (node instanceof StringNode) return "";
1242       Usage usage = node.getUsage();
1243       if (usage == MORE_USAGES_SEPARATOR || usage == USAGES_OUTSIDE_SCOPE_SEPARATOR) return "";
1244       GroupNode group = (GroupNode)node.getParent();
1245       return usage.getPresentation().getPlainText() + group;
1246     }
1247
1248     @Override
1249     protected void selectElement(Object element, String selectedText) {
1250       List<UsageNode> data = ((MyModel)getTable().getModel()).getItems();
1251       int i = data.indexOf(element);
1252       if (i == -1) return;
1253       final int viewRow = getTable().convertRowIndexToView(i);
1254       getTable().getSelectionModel().setSelectionInterval(viewRow, viewRow);
1255       TableUtil.scrollSelectionToVisible(getTable());
1256     }
1257
1258     private MyTable getTable() {
1259       return (MyTable)myComponent;
1260     }
1261   }
1262 }