1 package com
.intellij
.ide
.util
.gotoByName
;
3 import com
.intellij
.ide
.IdeBundle
;
4 import com
.intellij
.ide
.actions
.CopyReferenceAction
;
5 import com
.intellij
.ide
.ui
.UISettings
;
6 import com
.intellij
.openapi
.actionSystem
.*;
7 import com
.intellij
.openapi
.application
.ApplicationManager
;
8 import com
.intellij
.openapi
.application
.ModalityState
;
9 import com
.intellij
.openapi
.diagnostic
.Logger
;
10 import com
.intellij
.openapi
.editor
.colors
.EditorColorsManager
;
11 import com
.intellij
.openapi
.editor
.colors
.EditorColorsScheme
;
12 import com
.intellij
.openapi
.keymap
.KeymapManager
;
13 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
14 import com
.intellij
.openapi
.project
.Project
;
15 import com
.intellij
.openapi
.ui
.popup
.JBPopup
;
16 import com
.intellij
.openapi
.ui
.popup
.JBPopupFactory
;
17 import com
.intellij
.openapi
.util
.Comparing
;
18 import com
.intellij
.openapi
.util
.Pair
;
19 import com
.intellij
.openapi
.util
.SystemInfo
;
20 import com
.intellij
.openapi
.util
.text
.StringUtil
;
21 import com
.intellij
.openapi
.wm
.WindowManager
;
22 import com
.intellij
.openapi
.wm
.ex
.WindowManagerEx
;
23 import com
.intellij
.psi
.PsiElement
;
24 import com
.intellij
.psi
.codeStyle
.NameUtil
;
25 import com
.intellij
.psi
.statistics
.StatisticsInfo
;
26 import com
.intellij
.psi
.statistics
.StatisticsManager
;
27 import com
.intellij
.psi
.util
.proximity
.PsiProximityComparator
;
28 import com
.intellij
.ui
.DocumentAdapter
;
29 import com
.intellij
.ui
.ListScrollingUtil
;
30 import com
.intellij
.ui
.popup
.PopupOwner
;
31 import com
.intellij
.ui
.popup
.PopupUpdateProcessor
;
32 import com
.intellij
.util
.Alarm
;
33 import com
.intellij
.util
.SmartList
;
34 import com
.intellij
.util
.diff
.Diff
;
35 import com
.intellij
.util
.ui
.UIUtil
;
36 import org
.apache
.oro
.text
.regex
.*;
37 import org
.jetbrains
.annotations
.NonNls
;
38 import org
.jetbrains
.annotations
.Nullable
;
41 import javax
.swing
.event
.DocumentEvent
;
42 import javax
.swing
.event
.ListSelectionEvent
;
43 import javax
.swing
.event
.ListSelectionListener
;
44 import javax
.swing
.text
.DefaultEditorKit
;
46 import java
.awt
.event
.*;
47 import java
.lang
.ref
.Reference
;
48 import java
.lang
.ref
.WeakReference
;
50 import java
.util
.List
;
52 public abstract class ChooseByNameBase
{
53 private static final Logger LOG
= Logger
.getInstance("#com.intellij.ide.util.gotoByName.ChooseByNameBase");
55 protected final Project myProject
;
56 protected final ChooseByNameModel myModel
;
57 protected final String myInitialText
;
58 private final Reference
<PsiElement
> myContext
;
60 protected Component myPreviouslyFocusedComponent
;
62 protected JPanelProvider myTextFieldPanel
;// Located in the layered pane
63 protected JTextField myTextField
;
64 private JPanel myCardContainer
;
65 private CardLayout myCard
;
66 protected JCheckBox myCheckBox
;
67 /** the tool area of the popup, it is just after card box */
68 protected JComponent myToolArea
;
70 protected JScrollPane myListScrollPane
; // Located in the layered pane
71 protected JList myList
;
72 private DefaultListModel myListModel
;
73 private List
<Pair
<String
, Integer
>> myHistory
;
74 private List
<Pair
<String
, Integer
>> myFuture
;
76 protected ChooseByNamePopupComponent
.Callback myActionListener
;
78 protected final Alarm myAlarm
= new Alarm();
80 private final ListUpdater myListUpdater
= new ListUpdater();
82 private boolean myListIsUpToDate
= false;
83 protected boolean myDisposedFlag
= false;
85 private final String
[][] myNames
= new String
[2][];
86 private CalcElementsThread myCalcElementsThread
;
87 private static int VISIBLE_LIST_SIZE_LIMIT
= 10;
88 private static final int MAXIMUM_LIST_SIZE_LIMIT
= 30;
89 private int myMaximumListSizeLimit
= MAXIMUM_LIST_SIZE_LIMIT
;
90 @NonNls private static final String NOT_FOUND_IN_PROJECT_CARD
= "syslib";
91 @NonNls private static final String NOT_FOUND_CARD
= "nfound";
92 @NonNls private static final String CHECK_BOX_CARD
= "chkbox";
93 @NonNls private static final String SEARCHING_CARD
= "searching";
94 private static final int REBUILD_DELAY
= 300;
96 private final Alarm myHideAlarm
= new Alarm();
98 private static class MatchesComparator
implements Comparator
<String
> {
99 private final String myOriginalPattern
;
101 public MatchesComparator(final String originalPattern
) {
102 myOriginalPattern
= originalPattern
.trim();
105 public int compare(final String a
, final String b
) {
106 boolean aStarts
= a
.startsWith(myOriginalPattern
);
107 boolean bStarts
= b
.startsWith(myOriginalPattern
);
108 if (aStarts
&& bStarts
) return a
.compareToIgnoreCase(b
);
109 if (aStarts
&& !bStarts
) return -1;
110 if (bStarts
&& !aStarts
) return 1;
111 return a
.compareToIgnoreCase(b
);
116 * @param initialText initial text which will be in the lookup text field
119 protected ChooseByNameBase(Project project
, ChooseByNameModel model
, String initialText
, final PsiElement context
) {
122 myInitialText
= initialText
;
123 myContext
= new WeakReference
<PsiElement
>(context
);
127 * @return get tool area
129 public JComponent
getToolArea() {
134 * Set tool area. The method may be called only before invoke.
135 * @param toolArea a tool area component
137 public void setToolArea(JComponent toolArea
) {
139 throw new IllegalStateException("Tool area is modifiable only before invoke()");
141 myToolArea
= toolArea
;
144 public void invoke(final ChooseByNamePopupComponent
.Callback callback
, final ModalityState modalityState
, boolean allowMultipleSelection
) {
145 initUI(callback
, modalityState
, allowMultipleSelection
);
148 public class JPanelProvider
extends JPanel
implements DataProvider
{
149 JBPopup myHint
= null;
150 boolean myFocusRequested
= false;
152 JPanelProvider(LayoutManager mgr
) {
159 public Object
getData(String dataId
) {
160 if (!myListIsUpToDate
) {
163 if (dataId
.equals(DataConstants
.PSI_ELEMENT
)) {
164 Object element
= getChosenElement();
166 if (element
instanceof PsiElement
) {
170 if (element
instanceof DataProvider
) {
171 return ((DataProvider
)element
).getData(dataId
);
174 else if (dataId
.equals(DataConstants
.PSI_ELEMENT_ARRAY
)) {
175 final List
<Object
> chosenElements
= getChosenElements();
176 if (chosenElements
!= null) {
177 List
<PsiElement
> result
= new ArrayList
<PsiElement
>();
178 for (Object element
: chosenElements
) {
179 if (element
instanceof PsiElement
) {
180 result
.add((PsiElement
)element
);
183 return result
.toArray(new PsiElement
[result
.size()]);
186 else if (dataId
.equals(DataConstants
.DOMINANT_HINT_AREA_RECTANGLE
)) {
192 public void registerHint(JBPopup h
) {
193 if (myHint
!= null && myHint
.isVisible() && myHint
!= h
){
199 public boolean focusRequested() {
200 boolean focusRequested
= myFocusRequested
;
202 myFocusRequested
= false;
204 return focusRequested
;
207 public void requestFocus() {
208 myFocusRequested
= true;
211 public void unregisterHint() {
215 public void hideHint() {
216 if (myHint
!= null) {
221 public JBPopup
getHint() {
225 public void updateHint(PsiElement element
) {
226 if (myHint
== null || !myHint
.isVisible()) return;
227 final PopupUpdateProcessor updateProcessor
= myHint
.getUserData(PopupUpdateProcessor
.class);
228 if (updateProcessor
!= null){
230 updateProcessor
.updatePopup(element
);
237 * @param modalityState - if not null rebuilds list in given {@link ModalityState}
238 * @param allowMultipleSelection
240 protected void initUI(final ChooseByNamePopupComponent
.Callback callback
, final ModalityState modalityState
, boolean allowMultipleSelection
) {
241 myPreviouslyFocusedComponent
= WindowManagerEx
.getInstanceEx().getFocusedComponent(myProject
);
243 myActionListener
= callback
;
244 myTextFieldPanel
= new JPanelProvider();
245 myTextFieldPanel
.setLayout(new BoxLayout(myTextFieldPanel
, BoxLayout
.Y_AXIS
));
246 final JPanel hBox
= new JPanel();
247 hBox
.setLayout(new BoxLayout(hBox
, BoxLayout
.X_AXIS
));
249 if (myModel
.getPromptText() != null) {
250 JLabel label
= new JLabel(" " + myModel
.getPromptText());
251 label
.setFont(UIUtil
.getLabelFont().deriveFont(Font
.BOLD
));
255 myCard
= new CardLayout();
256 myCardContainer
= new JPanel(myCard
);
258 final JPanel checkBoxPanel
= new JPanel();
259 checkBoxPanel
.setBorder(BorderFactory
.createEmptyBorder(0, 0, 0, 5));
260 myCheckBox
= new JCheckBox(myModel
.getCheckBoxName());
261 myCheckBox
.setSelected(myModel
.loadInitialCheckBoxState());
263 if (myModel
.getPromptText() != null){
264 checkBoxPanel
.setLayout(new BoxLayout(checkBoxPanel
, BoxLayout
.X_AXIS
));
265 checkBoxPanel
.add (new JLabel (" ("));
266 checkBoxPanel
.add (myCheckBox
);
267 checkBoxPanel
.add (new JLabel (")"));
269 checkBoxPanel
.setLayout(new BoxLayout(checkBoxPanel
, BoxLayout
.LINE_AXIS
));
270 checkBoxPanel
.setComponentOrientation(ComponentOrientation
.RIGHT_TO_LEFT
);
271 checkBoxPanel
.add (new JLabel (")"));
272 checkBoxPanel
.add (myCheckBox
);
273 checkBoxPanel
.add (new JLabel (" ("));
275 checkBoxPanel
.setVisible(myModel
.getCheckBoxName() != null);
276 JPanel panel
= new JPanel(new BorderLayout());
277 panel
.add(checkBoxPanel
, BorderLayout
.CENTER
);
278 myCardContainer
.add(panel
, CHECK_BOX_CARD
);
279 myCardContainer
.add(new JLabel(" (" + myModel
.getNotInMessage() + ")"), NOT_FOUND_IN_PROJECT_CARD
);
280 myCardContainer
.add(new JLabel(" " + IdeBundle
.message("label.choosebyname.no.matches.found")), NOT_FOUND_CARD
);
281 myCardContainer
.add(new JLabel(" " + IdeBundle
.message("label.choosebyname.searching")), SEARCHING_CARD
);
282 myCard
.show(myCardContainer
, CHECK_BOX_CARD
);
284 //myCaseCheckBox = new JCheckBox("Ignore case");
285 //myCaseCheckBox.setMnemonic('g');
286 //myCaseCheckBox.setSelected(true);
288 //myCamelCheckBox = new JCheckBox("Camel words");
289 //myCamelCheckBox.setMnemonic('w');
290 //myCamelCheckBox.setSelected(true);
292 if (isCheckboxVisible()) {
293 hBox
.add(myCardContainer
);
294 //hBox.add(myCheckBox);
295 //hBox.add(myCaseCheckBox);
296 //hBox.add(myCamelCheckBox);
298 if(myToolArea
!= null) {
299 // if too area was set, add it to hbox
300 hBox
.add(myToolArea
);
302 myTextFieldPanel
.add(hBox
);
304 myHistory
= new ArrayList
<Pair
<String
, Integer
>>();
305 myFuture
= new ArrayList
<Pair
<String
, Integer
>>();
306 myTextField
= new MyTextField();
307 myTextField
.setText(myInitialText
);
309 final ActionMap actionMap
= new ActionMap();
310 actionMap
.setParent(myTextField
.getActionMap());
311 actionMap
.put(DefaultEditorKit
.copyAction
, new AbstractAction() {
312 public void actionPerformed(ActionEvent e
) {
313 if (myTextField
.getSelectedText() != null) {
314 actionMap
.getParent().get(DefaultEditorKit
.copyAction
).actionPerformed(e
);
317 final Object chosenElement
= getChosenElement();
318 if (chosenElement
instanceof PsiElement
) {
319 CopyReferenceAction
.doCopy((PsiElement
)chosenElement
, myProject
);
323 myTextField
.setActionMap(actionMap
);
325 myTextFieldPanel
.add(myTextField
);
326 EditorColorsScheme scheme
= EditorColorsManager
.getInstance().getGlobalScheme();
327 Font editorFont
= new Font(scheme
.getEditorFontName(), Font
.PLAIN
, scheme
.getEditorFontSize());
328 myTextField
.setFont(editorFont
);
330 if (isCloseByFocusLost()) {
331 myTextField
.addFocusListener(new FocusAdapter() {
332 public void focusLost(final FocusEvent e
) {
333 myHideAlarm
.addRequest(new Runnable() {
335 if (!JBPopupFactory
.getInstance().isChildPopupFocused(e
.getComponent())) {
344 myCheckBox
.addItemListener(new ItemListener() {
345 public void itemStateChanged(ItemEvent e
) {
349 myCheckBox
.setFocusable(false);
351 myTextField
.getDocument().addDocumentListener(new DocumentAdapter() {
352 protected void textChanged(DocumentEvent e
) {
357 myTextField
.addKeyListener(new KeyAdapter() {
358 public void keyPressed(KeyEvent e
) {
359 if (!myListScrollPane
.isVisible()) {
362 final int keyCode
= e
.getKeyCode();
364 case KeyEvent
.VK_DOWN
:
365 ListScrollingUtil
.moveDown(myList
, e
.getModifiersEx());
368 ListScrollingUtil
.moveUp(myList
, e
.getModifiersEx());
370 case KeyEvent
.VK_PAGE_UP
:
371 ListScrollingUtil
.movePageUp(myList
);
373 case KeyEvent
.VK_PAGE_DOWN
:
374 ListScrollingUtil
.movePageDown(myList
);
376 case KeyEvent
.VK_ENTER
:
377 if (myList
.getSelectedValue() == EXTRA_ELEM
) {
378 myMaximumListSizeLimit
+= MAXIMUM_LIST_SIZE_LIMIT
;
379 rebuildList(myList
.getSelectedIndex(), REBUILD_DELAY
, null, ModalityState
.current());
387 myTextField
.addActionListener(new ActionListener() {
388 public void actionPerformed(ActionEvent actionEvent
) {
393 myListModel
= new DefaultListModel();
394 myList
= new JList(myListModel
);
395 myList
.setFocusable(false);
396 myList
.setSelectionMode(allowMultipleSelection ? ListSelectionModel
.MULTIPLE_INTERVAL_SELECTION
:
397 ListSelectionModel
.SINGLE_SELECTION
);
398 myList
.addMouseListener(new MouseAdapter() {
399 public void mouseClicked(MouseEvent e
) {
400 if (!myTextField
.hasFocus()) {
401 myTextField
.requestFocus();
404 if (e
.getClickCount() == 2) {
405 if (myList
.getSelectedValue() == EXTRA_ELEM
) {
406 myMaximumListSizeLimit
+= MAXIMUM_LIST_SIZE_LIMIT
;
407 rebuildList(myList
.getSelectedIndex(), REBUILD_DELAY
, null, ModalityState
.current());
416 myList
.setCellRenderer(myModel
.getListCellRenderer());
417 myList
.setFont(editorFont
);
419 myList
.addListSelectionListener(new ListSelectionListener() {
420 public void valueChanged(ListSelectionEvent e
) {
421 choosenElementMightChange();
422 updateDocumentation();
426 myListScrollPane
= new JScrollPane(myList
);
428 if (!UIUtil
.isMotifLookAndFeel()) {
429 UIUtil
.installPopupMenuBorder(myTextFieldPanel
);
431 UIUtil
.installPopupMenuColorAndFonts(myTextFieldPanel
);
433 showTextFieldPanel();
435 if (modalityState
!= null) {
436 rebuildList(0, 0, null, modalityState
);
440 private void hideHint() {
441 if (!myTextFieldPanel
.focusRequested()) {
443 myTextFieldPanel
.hideHint();
448 * Default rebuild list. It uses {@link #REBUILD_DELAY} and current modality state.
450 public void rebuildList() {
451 // TODO this method is public, because the chooser does not listed for the model.
452 rebuildList(0, REBUILD_DELAY
, null, ModalityState
.current());
455 private void updateDocumentation() {
456 final JBPopup hint
= myTextFieldPanel
.getHint();
457 final Object element
= getChosenElement();
459 if (element
instanceof PsiElement
) {
460 myTextFieldPanel
.updateHint((PsiElement
)element
);
461 } else if (element
instanceof DataProvider
) {
462 final Object o
= ((DataProvider
)element
).getData(DataConstants
.PSI_ELEMENT
);
463 if (o
instanceof PsiElement
) {
464 myTextFieldPanel
.updateHint((PsiElement
)o
);
470 private void doClose(final boolean ok
) {
471 myListUpdater
.cancelAll();
475 private synchronized void ensureNamesLoaded(boolean checkboxState
) {
476 int index
= checkboxState ?
1 : 0;
477 if (myNames
[index
] != null) return;
479 Window window
= (Window
)SwingUtilities
.getAncestorOfClass(Window
.class, myTextField
);
480 //LOG.assertTrue (myTextField != null);
481 //LOG.assertTrue (window != null);
482 Window ownerWindow
= null;
483 if (window
!= null) {
484 window
.setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
485 ownerWindow
= window
.getOwner();
486 if (ownerWindow
!= null) {
487 ownerWindow
.setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
490 myNames
[index
] = myModel
.getNames(checkboxState
);
492 if (window
!= null) {
493 window
.setCursor(Cursor
.getDefaultCursor());
494 if (ownerWindow
!= null) {
495 ownerWindow
.setCursor(Cursor
.getDefaultCursor());
500 protected abstract boolean isCheckboxVisible();
502 protected abstract boolean isShowListForEmptyPattern();
504 protected abstract boolean isCloseByFocusLost();
506 protected void showTextFieldPanel() {
507 final JLayeredPane layeredPane
= getLayeredPane();
508 final Dimension preferredTextFieldPanelSize
= myTextFieldPanel
.getPreferredSize();
509 final int x
= (layeredPane
.getWidth() - preferredTextFieldPanelSize
.width
) / 2;
510 final int paneHeight
= layeredPane
.getHeight();
511 final int y
= paneHeight
/ 3 - preferredTextFieldPanelSize
.height
/ 2;
514 myTextFieldPanel
.setBounds(x
, y
, preferredTextFieldPanelSize
.width
, preferredTextFieldPanelSize
.height
);
515 layeredPane
.add(myTextFieldPanel
, Integer
.valueOf(500));
516 layeredPane
.moveToFront(myTextFieldPanel
);
517 VISIBLE_LIST_SIZE_LIMIT
= Math
.max
518 (10, (paneHeight
- (y
+ preferredTextFieldPanelSize
.height
)) / (preferredTextFieldPanelSize
.height
/ 2) - 1);
520 // I'm registering KeyListener to close popup only by KeyTyped event.
521 // If react on KeyPressed then sometime KeyTyped goes into underlying editor.
522 // It causes typing of Enter into it.
523 myTextFieldPanel
.registerKeyboardAction(new AbstractAction() {
524 public void actionPerformed(ActionEvent e
) {
527 }, KeyStroke
.getKeyStroke(KeyEvent
.VK_ESCAPE
, 0),
528 JComponent
.WHEN_IN_FOCUSED_WINDOW
531 myList
.registerKeyboardAction(new AbstractAction() {
532 public void actionPerformed(ActionEvent e
) {
535 }, KeyStroke
.getKeyStroke(KeyEvent
.VK_ESCAPE
, 0),
536 JComponent
.WHEN_IN_FOCUSED_WINDOW
539 if (myTextField
.requestFocusInWindow() || SystemInfo
.isMac
) {
540 myTextField
.requestFocus();
543 myTextFieldPanel
.validate();
544 myTextFieldPanel
.paintImmediately(0, 0, myTextFieldPanel
.getWidth(), myTextFieldPanel
.getHeight());
547 private JLayeredPane
getLayeredPane() {
548 JLayeredPane layeredPane
;
549 final Window window
= WindowManager
.getInstance().suggestParentWindow(myProject
);
550 if (window
instanceof JFrame
) {
551 layeredPane
= ((JFrame
)window
).getLayeredPane();
553 else if (window
instanceof JDialog
) {
554 layeredPane
= ((JDialog
)window
).getLayeredPane();
557 throw new IllegalStateException("cannot find parent window: project=" + myProject
+
558 (myProject
!= null ?
"; open=" + myProject
.isOpen() : "") +
559 "; window=" + window
);
564 private final Object myRebuildMutex
= new Object ();
566 protected void rebuildList(final int pos
, final int delay
, final Runnable postRunnable
, final ModalityState modalityState
) {
567 myListIsUpToDate
= false;
568 myAlarm
.cancelAllRequests();
569 myListUpdater
.cancelAll();
572 ApplicationManager
.getApplication().invokeLater(new Runnable() {
574 final String text
= myTextField
.getText();
575 if (!isShowListForEmptyPattern() && (text
== null || text
.trim().length() == 0)) {
578 myCard
.show(myCardContainer
, CHECK_BOX_CARD
);
581 final Runnable request
= new Runnable() {
583 final CalcElementsCallback callback
= new CalcElementsCallback() {
584 public void run(final Set
<?
> elements
) {
585 synchronized (myRebuildMutex
) {
586 ApplicationManager
.getApplication().assertIsDispatchThread();
587 if (myDisposedFlag
) {
591 setElementsToList(pos
, elements
);
593 myListIsUpToDate
= true;
594 choosenElementMightChange();
596 if (postRunnable
!= null) {
605 myCalcElementsThread
= new CalcElementsThread(text
, myCheckBox
.isSelected(), callback
, modalityState
);
606 myCalcElementsThread
.setCanCancel(postRunnable
== null);
607 ApplicationManager
.getApplication().executeOnPooledThread(myCalcElementsThread
);
612 myAlarm
.addRequest(request
, delay
, ModalityState
.stateForComponent(myTextField
));
621 private void tryToCancel() {
622 if (myCalcElementsThread
!= null) {
623 myCalcElementsThread
.cancel();
624 myCalcElementsThread
= null;
628 private void setElementsToList(int pos
, Set
<?
> elements
) {
629 myListUpdater
.cancelAll();
630 if (myDisposedFlag
) return;
631 if (elements
.isEmpty()) {
633 myTextField
.setForeground(Color
.red
);
634 myListUpdater
.cancelAll();
639 Object
[] oldElements
= myListModel
.toArray();
640 Object
[] newElements
= elements
.toArray();
641 Diff
.Change change
= Diff
.buildChanges(oldElements
, newElements
);
643 if (change
== null) return; // Nothing changed
645 List
<Cmd
> commands
= new ArrayList
<Cmd
>();
648 while (change
!= null) {
649 if (change
.deleted
> 0) {
650 final int start
= change
.line0
+ inserted
- deleted
;
651 commands
.add(new RemoveCmd(start
, start
+ change
.deleted
- 1));
654 if (change
.inserted
> 0) {
655 for (int i
= 0; i
< change
.inserted
; i
++) {
656 commands
.add(new InsertCmd(change
.line0
+ i
+ inserted
- deleted
, newElements
[change
.line1
+ i
]));
660 deleted
+= change
.deleted
;
661 inserted
+= change
.inserted
;
662 change
= change
.link
;
665 myTextField
.setForeground(UIUtil
.getTextFieldForeground());
666 if (!commands
.isEmpty()) {
668 myListUpdater
.appendToModel(commands
, pos
);
672 pos
= detectBestStatisticalPosition();
675 ListScrollingUtil
.selectItem(myList
, Math
.min(pos
, myListModel
.size() - 1));
676 myList
.setVisibleRowCount(Math
.min(VISIBLE_LIST_SIZE_LIMIT
, myList
.getModel().getSize()));
681 private int detectBestStatisticalPosition() {
683 int bestPosition
= 0;
684 final int count
= myListModel
.getSize();
686 final String statContext
= statisticsContext();
687 for (int i
= 0; i
< count
; i
++) {
688 final Object modelElement
= myListModel
.getElementAt(i
);
689 String text
= EXTRA_ELEM
.equals(modelElement
) ?
null : myModel
.getFullName(modelElement
);
691 int stats
= StatisticsManager
.getInstance().getUseCount(new StatisticsInfo(statContext
, text
));
702 protected String
statisticsContext() {
703 return "choose_by_name#"+myModel
.getPromptText()+"#"+ myCheckBox
.isSelected() + "#" + myTextField
.getText();
706 private String
getQualifierPattern(String pattern
) {
707 final String
[] separators
= myModel
.getSeparators();
708 int lastSeparatorOccurence
= 0;
709 for (String separator
: separators
) {
710 lastSeparatorOccurence
= Math
.max(lastSeparatorOccurence
, pattern
.lastIndexOf(separator
));
712 return pattern
.substring(0, lastSeparatorOccurence
);
715 public String
getNamePattern(String pattern
) {
716 final String
[] separators
= myModel
.getSeparators();
717 int lastSeparatorOccurence
= 0;
718 for (String separator
: separators
) {
719 final int idx
= pattern
.lastIndexOf(separator
);
720 lastSeparatorOccurence
= Math
.max(lastSeparatorOccurence
, idx
== -1 ? idx
: idx
+ separator
.length());
723 return pattern
.substring(lastSeparatorOccurence
);
726 private interface Cmd
{
730 private class RemoveCmd
implements Cmd
{
731 private final int start
;
732 private final int end
;
734 public RemoveCmd(final int start
, final int end
) {
739 public void apply() {
740 myListModel
.removeRange(start
, end
);
744 private class InsertCmd
implements Cmd
{
745 private final int idx
;
746 private final Object element
;
748 public InsertCmd(final int idx
, final Object element
) {
750 this.element
= element
;
753 public void apply() {
754 if (idx
< myListModel
.size()) {
755 myListModel
.add(idx
, element
);
758 myListModel
.addElement(element
);
763 private class ListUpdater
{
764 private final Alarm myAlarm
= new Alarm(Alarm
.ThreadToUse
.SWING_THREAD
);
765 private static final int DELAY
= 10;
766 private static final int MAX_BLOCKING_TIME
= 30;
767 private final List
<Cmd
> myCommands
= Collections
.synchronizedList(new ArrayList
<Cmd
>());
769 public void cancelAll() {
771 myAlarm
.cancelAllRequests();
774 public void appendToModel(final List
<Cmd
> commands
, final int selectionPos
) {
775 myAlarm
.cancelAllRequests();
776 myCommands
.addAll(commands
);
778 if (myCommands
.isEmpty() || myDisposedFlag
) return;
779 myAlarm
.addRequest(new Runnable() {
781 if (myDisposedFlag
) return;
782 final long startTime
= System
.currentTimeMillis();
783 while (!myCommands
.isEmpty() && System
.currentTimeMillis() - startTime
< MAX_BLOCKING_TIME
) {
784 final Cmd cmd
= myCommands
.remove(0);
788 myList
.setVisibleRowCount(Math
.min(VISIBLE_LIST_SIZE_LIMIT
, myList
.getModel().getSize()));
789 if (!myListModel
.isEmpty()) {
790 int pos
= selectionPos
== 0 ?
detectBestStatisticalPosition() : selectionPos
;
791 ListScrollingUtil
.selectItem(myList
, Math
.min(pos
, myListModel
.size() - 1));
794 if (!myCommands
.isEmpty()) {
795 myAlarm
.addRequest(this, DELAY
);
803 protected abstract void showList();
805 protected abstract void hideList();
807 protected abstract void close(boolean isOk
);
810 public Object
getChosenElement() {
811 final List
<Object
> elements
= getChosenElements();
812 return elements
!= null && elements
.size() == 1 ? elements
.get(0) : null;
815 protected List
<Object
> getChosenElements() {
816 if (myListIsUpToDate
) {
817 List
<Object
> values
= new ArrayList
<Object
>(Arrays
.asList(myList
.getSelectedValues()));
818 values
.remove(EXTRA_ELEM
);
822 final String text
= myTextField
.getText();
823 final boolean checkBoxState
= myCheckBox
.isSelected();
824 //ensureNamesLoaded(checkBoxState);
825 final String
[] names
= checkBoxState ? myNames
[1] : myNames
[0];
826 if (names
== null) return Collections
.emptyList();
828 Object uniqueElement
= null;
830 for (final String name
: names
) {
831 if (text
.equalsIgnoreCase(name
)) {
832 final Object
[] elements
= myModel
.getElementsByName(name
, checkBoxState
, text
);
833 if (elements
.length
> 1) return Collections
.emptyList();
834 if (elements
.length
== 0) continue;
835 if (uniqueElement
!= null) return Collections
.emptyList();
836 uniqueElement
= elements
[0];
839 return uniqueElement
== null ? Collections
.emptyList() : Collections
.singletonList(uniqueElement
);
842 protected void choosenElementMightChange() {
845 private final class MyTextField
extends JTextField
implements PopupOwner
{
846 private final KeyStroke myCompletionKeyStroke
;
847 private final KeyStroke forwardStroke
;
848 private final KeyStroke backStroke
;
850 public MyTextField() {
852 enableEvents(AWTEvent
.KEY_EVENT_MASK
);
853 myCompletionKeyStroke
= getShortcut(IdeActions
.ACTION_CODE_COMPLETION
);
854 forwardStroke
= getShortcut(IdeActions
.ACTION_GOTO_FORWARD
);
855 backStroke
= getShortcut(IdeActions
.ACTION_GOTO_BACK
);
859 private KeyStroke
getShortcut(String actionCodeCompletion
) {
860 final Shortcut
[] shortcuts
= KeymapManager
.getInstance().getActiveKeymap().getShortcuts(actionCodeCompletion
);
861 for (final Shortcut shortcut
: shortcuts
) {
862 if (shortcut
instanceof KeyboardShortcut
) {
863 return ((KeyboardShortcut
)shortcut
).getFirstKeyStroke();
869 protected void processKeyEvent(KeyEvent e
) {
870 final KeyStroke keyStroke
= KeyStroke
.getKeyStrokeForEvent(e
);
871 if (myCompletionKeyStroke
!= null && keyStroke
.equals(myCompletionKeyStroke
)) {
873 final String pattern
= myTextField
.getText();
874 final String oldText
= myTextField
.getText();
875 final int oldPos
= myList
.getSelectedIndex();
876 myHistory
.add(Pair
.create(oldText
, oldPos
));
877 final Runnable postRunnable
= new Runnable() {
879 fillInCommonPrefix(pattern
);
882 rebuildList(0, 0, postRunnable
, ModalityState
.current());
885 if (backStroke
!= null && keyStroke
.equals(backStroke
)) {
887 if (!myHistory
.isEmpty()) {
888 final String oldText
= myTextField
.getText();
889 final int oldPos
= myList
.getSelectedIndex();
890 final Pair
<String
, Integer
> last
= myHistory
.remove(myHistory
.size() - 1);
891 myTextField
.setText(last
.first
);
892 myFuture
.add(Pair
.create(oldText
, oldPos
));
893 rebuildList(0, 0, null, ModalityState
.current());
897 if (forwardStroke
!= null && keyStroke
.equals(forwardStroke
)) {
899 if (!myFuture
.isEmpty()) {
900 final String oldText
= myTextField
.getText();
901 final int oldPos
= myList
.getSelectedIndex();
902 final Pair
<String
, Integer
> next
= myFuture
.remove(myFuture
.size() - 1);
903 myTextField
.setText(next
.first
);
904 myHistory
.add(Pair
.create(oldText
, oldPos
));
905 rebuildList(0, 0, null, ModalityState
.current());
909 super.processKeyEvent(e
);
912 private void fillInCommonPrefix(final String pattern
) {
913 final ArrayList
<String
> list
= new ArrayList
<String
>();
914 getNamesByPattern(myCheckBox
.isSelected(), null, list
, pattern
);
916 if (isComplexPattern(pattern
)) return; //TODO: support '*'
917 final String oldText
= myTextField
.getText();
918 final int oldPos
= myList
.getSelectedIndex();
920 String commonPrefix
= null;
921 if (!list
.isEmpty()) {
922 for (String name
: list
) {
923 final String string
= name
.toLowerCase();
924 if (commonPrefix
== null) {
925 commonPrefix
= string
;
928 while (commonPrefix
.length() > 0) {
929 if (string
.startsWith(commonPrefix
)) {
932 commonPrefix
= commonPrefix
.substring(0, commonPrefix
.length() - 1);
934 if (commonPrefix
.length() == 0) break;
937 commonPrefix
= list
.get(0).substring(0, commonPrefix
.length());
938 for (int i
= 1; i
< list
.size(); i
++) {
939 final String string
= list
.get(i
).substring(0, commonPrefix
.length());
940 if (!string
.equals(commonPrefix
)) {
941 commonPrefix
= commonPrefix
.toLowerCase();
946 if (commonPrefix
== null) commonPrefix
= "";
947 final String newPattern
= commonPrefix
;
949 myHistory
.add(Pair
.create(oldText
, oldPos
));
950 myTextField
.setText(newPattern
);
951 myTextField
.setCaretPosition(newPattern
.length());
956 private boolean isComplexPattern(final String pattern
) {
957 if (pattern
.indexOf('*') >= 0) return true;
958 for (String s
: myModel
.getSeparators()) {
959 if (pattern
.contains(s
)) return true;
966 public Point
getBestPopupPosition() {
967 return new Point(myTextFieldPanel
.getWidth(), getHeight());
970 protected void paintComponent(final Graphics g
) {
971 UISettings
.setupAntialiasing(g
);
972 super.paintComponent(g
);
976 private static final String EXTRA_ELEM
= "...";
978 private class CalcElementsThread
implements Runnable
{
979 private final String myPattern
;
980 private boolean myCheckboxState
;
981 private final CalcElementsCallback myCallback
;
982 private final ModalityState myModalityState
;
984 private Set
<Object
> myElements
= null;
986 private volatile boolean myCancelled
= false;
987 private boolean myCanCancel
= true;
989 public CalcElementsThread(String pattern
, boolean checkboxState
, CalcElementsCallback callback
, ModalityState modalityState
) {
991 myCheckboxState
= checkboxState
;
992 myCallback
= callback
;
993 myModalityState
= modalityState
;
996 private final Alarm myShowCardAlarm
= new Alarm();
998 showCard(SEARCHING_CARD
, 200);
1000 final Set
<Object
> elements
= new LinkedHashSet
<Object
>();
1001 Runnable action
= new Runnable() {
1004 ensureNamesLoaded(myCheckboxState
);
1005 addElementsByPattern(elements
, myPattern
);
1006 for (Object elem
: elements
) {
1007 if (myCancelled
) throw new ProcessCanceledException();
1008 if (elem
instanceof PsiElement
) {
1009 final PsiElement psiElement
= (PsiElement
)elem
;
1010 psiElement
.isWritable(); // That will cache writable flag in VirtualFile. Taking the action here makes it canceleable.
1014 catch (ProcessCanceledException e
) {
1019 ApplicationManager
.getApplication().runReadAction(action
);
1022 myShowCardAlarm
.cancelAllRequests();
1026 final String cardToShow
;
1027 if (elements
.isEmpty() && !myCheckboxState
) {
1028 myCheckboxState
= true;
1029 ApplicationManager
.getApplication().runReadAction(action
);
1030 cardToShow
= elements
.isEmpty() ? NOT_FOUND_CARD
: NOT_FOUND_IN_PROJECT_CARD
;
1033 cardToShow
= elements
.isEmpty() ? NOT_FOUND_CARD
: CHECK_BOX_CARD
;
1035 showCard(cardToShow
, 0);
1037 myElements
= elements
;
1039 ApplicationManager
.getApplication().invokeLater(new Runnable() {
1041 myCallback
.run(myElements
);
1043 }, myModalityState
);
1046 private void showCard(final String card
, final int delay
) {
1047 myShowCardAlarm
.cancelAllRequests();
1048 myShowCardAlarm
.addRequest(new Runnable() {
1050 myCard
.show(myCardContainer
, card
);
1052 }, delay
, myModalityState
);
1055 public void setCanCancel(boolean canCancel
) {
1056 myCanCancel
= canCancel
;
1059 private void addElementsByPattern(Set
<Object
> elementsArray
, String pattern
) {
1060 String namePattern
= getNamePattern(pattern
);
1061 String qualifierPattern
= getQualifierPattern(pattern
);
1063 boolean empty
= namePattern
.length() == 0 || namePattern
.equals("@"); // TODO[yole]: remove implicit dependency
1064 if (empty
&& !isShowListForEmptyPattern()) return;
1066 List
<String
> namesList
= new ArrayList
<String
>();
1067 getNamesByPattern(myCheckboxState
, this, namesList
, namePattern
);
1069 throw new ProcessCanceledException();
1071 Collections
.sort(namesList
, new MatchesComparator(pattern
));
1073 boolean overflow
= false;
1074 List
<Object
> sameNameElements
= new SmartList
<Object
>();
1076 for (String name
: namesList
) {
1078 throw new ProcessCanceledException();
1080 final Object
[] elements
= myModel
.getElementsByName(name
, myCheckboxState
, namePattern
);
1081 if (elements
.length
> 1) {
1082 sameNameElements
.clear();
1083 for (final Object element
: elements
) {
1084 if (matchesQualifier(element
, qualifierPattern
)) {
1085 sameNameElements
.add(element
);
1088 sortByProximity(sameNameElements
);
1089 for (Object element
: sameNameElements
) {
1090 elementsArray
.add(element
);
1091 if (elementsArray
.size() >= myMaximumListSizeLimit
) {
1097 else if (elements
.length
== 1 && matchesQualifier(elements
[0], qualifierPattern
)) {
1098 elementsArray
.add(elements
[0]);
1099 if (elementsArray
.size() >= myMaximumListSizeLimit
) {
1107 elementsArray
.add(EXTRA_ELEM
);
1111 private void cancel() {
1118 private void sortByProximity(final List
<Object
> sameNameElements
) {
1119 Collections
.sort(sameNameElements
, new PathProximityComparator(myModel
, myContext
.get()));
1122 private List
<String
> split(String s
) {
1123 for (String separator
: myModel
.getSeparators()) {
1124 final List
<String
> result
= StringUtil
.split(s
, separator
);
1125 if (!result
.isEmpty()) return result
;
1127 return Collections
.singletonList(s
);
1130 private boolean matchesQualifier(final Object element
, final String qualifierPattern
) {
1131 final String name
= myModel
.getFullName(element
);
1132 if (name
== null) return false;
1134 final List
<String
> suspects
= split(name
);
1135 final List
<String
> patterns
= split(qualifierPattern
);
1137 int matchPosition
= 0;
1140 for (String pattern
: patterns
) {
1141 if (pattern
.length() > 0) {
1142 for (int j
= matchPosition
; j
< suspects
.size() - 1; j
++) {
1143 String suspect
= suspects
.get(j
);
1144 if (StringUtil
.startsWithIgnoreCase(suspect
, pattern
)) {
1145 matchPosition
= j
+ 1;
1157 private void getNamesByPattern(final boolean checkboxState
,
1158 CalcElementsThread calcElementsThread
,
1159 final List
<String
> list
,
1160 String pattern
) throws ProcessCanceledException
{
1161 if (!isShowListForEmptyPattern()) {
1162 LOG
.assertTrue(pattern
.length() > 0);
1165 if (pattern
.startsWith("@")) {
1166 pattern
= pattern
.substring(1);
1169 final String
[] names
= checkboxState ? myNames
[1] : myNames
[0];
1170 final String regex
= NameUtil
.buildRegexp(pattern
, 0, true, true);
1173 Perl5Compiler compiler
= new Perl5Compiler();
1174 final Pattern compiledPattern
= compiler
.compile(regex
);
1175 final PatternMatcher matcher
= new Perl5Matcher();
1177 for (String name
: names
) {
1178 if (calcElementsThread
!= null && calcElementsThread
.myCancelled
) {
1179 throw new ProcessCanceledException();
1182 if (myModel
instanceof CustomMatcherModel
) {
1183 if (((CustomMatcherModel
)myModel
).matches(name
, pattern
)) {
1187 else if (pattern
.length() == 0 || matcher
.matches(name
, compiledPattern
)) {
1193 catch (MalformedPatternException e
) {
1194 // Do nothing. No matches appears valid result for "bad" pattern
1198 private interface CalcElementsCallback
{
1199 void run(Set
<?
> elements
);
1202 private static class PathProximityComparator
implements Comparator
<Object
> {
1203 private final ChooseByNameModel myModel
;
1204 private final PsiProximityComparator myProximityComparator
;
1206 private PathProximityComparator(final ChooseByNameModel model
, final PsiElement context
) {
1208 myProximityComparator
= new PsiProximityComparator(context
);
1211 public int compare(final Object o1
, final Object o2
) {
1212 int rc
= myProximityComparator
.compare(o1
, o2
);
1213 if (rc
!= 0) return rc
;
1215 return Comparing
.compare(myModel
.getFullName(o1
), myModel
.getFullName(o2
));